Mozilla/mozilla/netwerk/dns/src/nsDnsService.cpp
darin%netscape.com 3deecbd85f fixes bug 141779 "Trunk, M1RC1 Crashes related to DNS caching [@ nsDNSRequest::FireStop]"
r=gordon sr=rpotts


git-svn-id: svn://10.0.0.236/trunk@121889 18797224-902f-48f8-a5cc-f745e15eee43
2002-05-20 21:31:27 +00:00

2001 lines
58 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: NPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Netscape 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://www.mozilla.org/NPL/
*
* 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 mozilla.org code.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1999
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the NPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the NPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#ifdef HAVE_RES_NINIT
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <arpa/nameser.h>
#include <resolv.h>
#endif
#include "nsDnsService.h"
#include "nsIDNSListener.h"
#include "nsIRequest.h"
#include "nsIObserverService.h"
#include "nsIPrefService.h"
#include "nsIPrefBranch.h"
#include "nsIPrefBranchInternal.h"
#include "nsIRequestObserver.h"
#include "nsIServiceManager.h"
#include "nsXPIDLString.h"
#include "nsTime.h"
#include "nsCRT.h"
#include "nsError.h"
#include "prnetdb.h"
#include "nsString.h"
#include "netCore.h"
#include "nsAutoLock.h"
#ifdef DNS_TIMING
#include "prinrval.h"
#include "prtime.h"
#endif
#include "prsystem.h"
#include "nsNetCID.h"
/******************************************************************************
* Platform specific defines and includes
*****************************************************************************/
/**
* XP_MAC
*/
#if defined(XP_MAC)
#include "pprthred.h"
#if TARGET_CARBON
#define INIT_OPEN_TRANSPORT() InitOpenTransportInContext(kInitOTForExtensionMask, &mClientContext)
#define OT_OPEN_INTERNET_SERVICES(config, flags, err) OTOpenInternetServicesInContext(config, flags, err, mClientContext)
#define OT_OPEN_ENDPOINT(config, flags, info, err) OTOpenEndpointInContext(config, flags, info, err, mClientContext)
#define CLOSE_OPEN_TRANSPORT() do { if (mClientContext) CloseOpenTransportInContext(mClientContext); } while (0)
#else
#define INIT_OPEN_TRANSPORT() InitOpenTransport()
#define OT_OPEN_INTERNET_SERVICES(config, flags, err) OTOpenInternetServices(config, flags, err)
#define OT_OPEN_ENDPOINT(config, flags, info, err) OTOpenEndpoint(config, flags, info, err)
#define CLOSE_OPEN_TRANSPORT() CloseOpenTransport()
#endif /* TARGET_CARBON */
#endif /* XP_MAC */
#if defined(XP_BEOS) && defined(BONE_VERSION)
#include <arpa/inet.h>
#endif
/**
* XP_UNIX
*/
/**
* XP_WIN
*/
#if defined(XP_WIN)
#define WM_DNS_SHUTDOWN (WM_USER + 200)
#endif /* XP_WIN */
/******************************************************************************
* nsDNSRequest
*****************************************************************************/
#ifdef XP_MAC
#pragma mark CLASSES
#pragma mark nsDNSRequest
#endif
class nsDNSRequest
: public PRCList
, public nsIRequest
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIREQUEST
nsDNSRequest(nsDNSLookup * lookup,
nsIDNSListener * userListener,
nsISupports * userContext)
: mUserListener(userListener)
, mUserContext(userContext)
, mLookup(lookup)
, mStatus(NS_OK)
#ifdef DNS_TIMING
, mStartTime(PR_IntervalNow())
#endif
{
NS_INIT_REFCNT();
PR_INIT_CLIST(this);
}
virtual ~nsDNSRequest()
{
if (!PR_CLIST_IS_EMPTY(this)) {
nsDNSService::Lock();
PR_REMOVE_AND_INIT_LINK(this);
nsDNSService::Unlock();
}
}
nsresult FireStart();
nsresult FireStop(nsresult status);
protected:
nsCOMPtr<nsIDNSListener> mUserListener;
nsCOMPtr<nsISupports> mUserContext;
nsDNSLookup* mLookup; // weak ref
nsresult mStatus;
#ifdef DNS_TIMING
PRIntervalTime mStartTime;
#endif
};
/******************************************************************************
* nsDNSLookup
*****************************************************************************/
#ifdef XP_MAC
#pragma mark nsDNSLookup
#endif
class nsDNSLookup
: public PRCList
, public nsISupports
{
public:
NS_DECL_ISUPPORTS
static
nsDNSLookup * Create(const char * hostName);
virtual ~nsDNSLookup();
const char * HostName() { return mHostName; }
nsHostEnt * HostEntry() { return &mHostEntry; }
PRBool IsNew() { return mState == LOOKUP_NEW; }
PRBool IsComplete() { return mState == LOOKUP_COMPLETE; }
PRBool IsNotProcessing() { return mProcessingRequests == 0; }
PRBool IsNotCacheable() { return (mFlags & eCacheableMask) == 0; }
void MarkNotCacheable() { mFlags &= ~eCacheableMask; }
void MarkCacheable() { mFlags |= eCacheableMask; }
PRBool IsNotEvicted() { return (mFlags & eEvictedMask) == 0; }
void MarkEvicted() { mFlags |= eEvictedMask; }
PRBool IsExpired();
nsresult Status() { return mStatus; }
void SetStatus( nsresult status) { mStatus = status; }
void Reset();
PRBool HostnameIsIPAddress();
nsresult InitiateLookup();
void MarkComplete( nsresult status);
nsresult EnqueueRequest( nsDNSRequest * request);
void ProcessRequests();
private:
friend class nsDNSService;
nsDNSLookup();
PRCList mRequestQ;
char * mHostName;
// Result of the DNS Lookup
nsHostEnt mHostEntry;
nsresult mStatus;
PRUint32 mState;
enum {
LOOKUP_NEW = 0,
LOOKUP_PENDING = 1,
LOOKUP_COMPLETE = 2
};
PRUint32 mProcessingRequests;
PRUint32 mFlags;
enum {
eCacheableMask = 0x000001,
eEvictedMask = 0x000010
};
nsTime mExpires;
// Platform specific portions
public:
#if defined(XP_MAC)
friend pascal
void nsDnsServiceNotifierRoutine(void * contextPtr,
OTEventCode code,
OTResult result,
void * cookie);
public:
void ConvertHostEntry();
PRBool mStringToAddrComplete;
private:
InetHostInfo mInetHostInfo;
#define GET_LOOKUP_FROM_ELEM(_this) \
((nsDNSLookup*)((char*)(_this) - offsetof(nsDNSLookup, mLookupElement)))
#define GET_LOOKUP_FROM_HOSTINFO(_this) \
((nsDNSLookup*)((char*)(_this) - offsetof(nsDNSLookup, mInetHostInfo)))
#endif
#if defined(XP_WIN)
friend static
LRESULT CALLBACK nsDNSEventProc(HWND hwnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam);
HANDLE mLookupHandle;
PRUint32 mMsgID;
#endif
#if defined(XP_UNIX) || defined(XP_BEOS) || defined(XP_OS2)
void DoSyncLookup();
PRStatus DoSyncLookupInternal();
#endif
};
/******************************************************************************
* utility routines:
*****************************************************************************/
#ifdef XP_MAC
#pragma mark -
#pragma mark UTILITY ROUTINES
#endif
/**
* BufAlloc
*
* Allocate space from the buffer, aligning it to "align" before doing
* the allocation. "align" must be a power of 2.
* NOTE: this code was taken from NSPR.
*/
static char *
BufAlloc(PRIntn amount, char **bufp, PRIntn *buflenp, PRIntn align) {
char *buf = *bufp;
PRIntn buflen = *buflenp;
if (align && ((long)buf & (align - 1))) {
PRIntn skip = align - ((ptrdiff_t)buf & (align - 1));
if (buflen < skip) {
return 0;
}
buf += skip;
buflen -= skip;
}
if (buflen < amount) {
return 0;
}
*bufp = buf + amount;
*buflenp = buflen - amount;
return buf;
}
struct DNSHashTableEntry : PLDHashEntryHdr {
nsDNSLookup * mLookup;
};
static const void * PR_CALLBACK
GetKey(PLDHashTable * /*table*/, PLDHashEntryHdr * header)
{
return (void*) ((DNSHashTableEntry *)header)->mLookup->HostName();
}
static PRBool PR_CALLBACK
MatchEntry(PLDHashTable * /* table */,
const PLDHashEntryHdr * header,
const void * key)
{
const char * hostName = ((DNSHashTableEntry *) header)->mLookup->HostName();
return nsCRT::strcmp(hostName, (const char *)key) == 0;
}
static void PR_CALLBACK
MoveEntry(PLDHashTable * /* table */,
const PLDHashEntryHdr * src,
PLDHashEntryHdr * dst)
{
((DNSHashTableEntry *)dst)->mLookup = ((DNSHashTableEntry *)src)->mLookup;
}
static void PR_CALLBACK
ClearEntry(PLDHashTable * /* table */,
PLDHashEntryHdr * header)
{
((DNSHashTableEntry *)header)->mLookup = nsnull;
}
#ifdef XP_MAC
#pragma mark -
#pragma mark nsDNSRequest
#endif
/******************************************************************************
* nsDNSRequest methods:
*****************************************************************************/
NS_IMPL_THREADSAFE_ISUPPORTS1(nsDNSRequest, nsIRequest);
nsresult
nsDNSRequest::FireStart()
{
nsresult rv;
NS_ASSERTION(mUserListener, "calling FireStart more than once");
if (mUserListener == nsnull)
return NS_ERROR_FAILURE;
rv = mUserListener->OnStartLookup(mUserContext, mLookup->HostName());
return rv;
}
nsresult
nsDNSRequest::FireStop(nsresult status)
{
nsresult rv;
const char * hostName = nsnull;
nsHostEnt * hostEnt = nsnull;
mStatus = status;
NS_ASSERTION(mLookup, "FireStop called with no mLookup.");
if (mLookup) {
hostName = mLookup->HostName();
hostEnt = mLookup->HostEntry();
} else if (NS_SUCCEEDED(mStatus)) {
mStatus = NS_ERROR_FAILURE; // skip calling OnFound()
}
mLookup = nsnull;
NS_ASSERTION(mUserListener, "calling FireStop more than once");
if (mUserListener == nsnull)
return NS_ERROR_FAILURE;
if (NS_SUCCEEDED(mStatus)) {
rv = mUserListener->OnFound(mUserContext, hostName, hostEnt);
NS_ASSERTION(NS_SUCCEEDED(rv), "OnFound failed");
}
rv = mUserListener->OnStopLookup(mUserContext, hostName, mStatus);
NS_ASSERTION(NS_SUCCEEDED(rv), "OnStopLookup failed");
mUserListener = nsnull;
mUserContext = nsnull;
#ifdef DNS_TIMING
if (nsDNSService::gService->mOut) {
PRIntervalTime stopTime = PR_IntervalNow();
double duration = PR_IntervalToMicroseconds(stopTime - mStartTime);
nsDNSService::gService->mCount++;
nsDNSService::gService->mTimes += duration;
nsDNSService::gService->mSquaredTimes += duration * duration;
fprintf(nsDNSService::gService->mOut, "DNS time #%d: %u us for %s\n",
(PRInt32)nsDNSService::gService->mCount,
(PRInt32)duration, mLookup->HostName());
}
#endif
return NS_OK;
}
NS_IMETHODIMP
nsDNSRequest::GetName(nsACString & result)
{
NS_NOTREACHED("nsDNSRequest::GetName");
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsDNSRequest::IsPending(PRBool * result)
{
if (mLookup) *result = !mLookup->IsComplete();
else *result = PR_FALSE; // XXX or error
return NS_OK;
}
NS_IMETHODIMP
nsDNSRequest::GetStatus(nsresult * status)
{
*status = mStatus;
return NS_OK;
}
NS_IMETHODIMP
nsDNSRequest::Cancel(nsresult status)
{
NS_ASSERTION(NS_FAILED(status), "shouldn't cancel with a success code");
PRBool ignore = PR_FALSE;
nsDNSService::Lock();
if (PR_CLIST_IS_EMPTY(this)) {
// ignore this cancelation since we've already called OnStopLookup or
// are in the process of calling OnStopLookup (on a different thread).
ignore = PR_TRUE;
} else {
PR_REMOVE_AND_INIT_LINK(this);
ignore = (mUserListener == nsnull);
}
nsDNSService::Unlock();
if (ignore)
return NS_OK;
return FireStop(status);
}
NS_IMETHODIMP
nsDNSRequest::Suspend()
{
NS_NOTYETIMPLEMENTED("Suspend() not supported by nsDNSRequest");
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsDNSRequest::Resume()
{
NS_NOTYETIMPLEMENTED("Resume() not supported by nsDNSRequest");
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsDNSRequest::GetLoadGroup(nsILoadGroup ** loadGroup)
{
*loadGroup = nsnull;
return NS_OK;
}
NS_IMETHODIMP
nsDNSRequest::SetLoadGroup(nsILoadGroup * loadGroup)
{
NS_NOTYETIMPLEMENTED("SetLoadGroup() not supported by nsDNSRequest");
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsDNSRequest::GetLoadFlags(nsLoadFlags * loadFlags)
{
*loadFlags = nsIRequest::LOAD_NORMAL;
return NS_OK;
}
NS_IMETHODIMP
nsDNSRequest::SetLoadFlags(nsLoadFlags loadFlags)
{
NS_NOTYETIMPLEMENTED("SetLoadFlags() not supported by nsDNSRequest");
return NS_ERROR_NOT_IMPLEMENTED;
}
/******************************************************************************
* nsDNSLookup methods:
*****************************************************************************/
#ifdef XP_MAC
#pragma mark -
#pragma mark nsDNSLookup
#endif
NS_IMPL_THREADSAFE_ISUPPORTS0(nsDNSLookup);
nsDNSLookup::nsDNSLookup()
: mHostName(nsnull)
, mStatus(NS_OK)
, mState(LOOKUP_NEW)
, mProcessingRequests(0)
, mFlags(eCacheableMask)
, mExpires(0)
{
NS_INIT_REFCNT();
MOZ_COUNT_CTOR(nsDNSLookup);
PR_INIT_CLIST(this);
PR_INIT_CLIST(&mRequestQ);
Reset();
}
nsDNSLookup *
nsDNSLookup::Create(const char * hostName)
{
nsDNSLookup * lookup = new nsDNSLookup;
if (!lookup) return nsnull;
lookup->mHostName = nsCRT::strdup(hostName);
if (lookup->mHostName == nsnull) {
delete lookup;
return nsnull;
}
NS_ADDREF(lookup);
return lookup;
}
void
nsDNSLookup::Reset()
{
// Initialize result holders
mHostEntry.bufLen = PR_NETDB_BUF_SIZE;
mHostEntry.bufPtr = mHostEntry.buffer;
mState = LOOKUP_NEW;
mStatus = NS_OK;
mExpires = nsTime() + nsInt64(nsDNSService::ExpirationInterval() * 1000000);
#if defined(XP_MAC)
mStringToAddrComplete = PR_FALSE;
#endif
#if defined(XP_WIN)
mLookupHandle = nsnull;
mMsgID = 0;
#endif
}
nsDNSLookup::~nsDNSLookup()
{
MOZ_COUNT_DTOR(nsDNSLookup);
NS_ASSERTION(PR_CLIST_IS_EMPTY(this), "lookup deleted while on list");
NS_ASSERTION(PR_CLIST_IS_EMPTY(&mRequestQ), "lookup deleted while requests pending");
if (mHostName) nsCRT::free(mHostName);
}
#if defined(XP_MAC)
void
nsDNSLookup::ConvertHostEntry()
{
PRIntn len, count, i;
// convert InetHostInfo to PRHostEnt
// expect the worse
mStatus = NS_ERROR_OUT_OF_MEMORY;
// copy name
len = nsCRT::strlen(mInetHostInfo.name);
mHostEntry.hostEnt.h_name = BufAlloc(len + 1, &mHostEntry.bufPtr, &mHostEntry.bufLen, 0);
NS_ASSERTION(nsnull != mHostEntry.hostEnt.h_name,"nsHostEnt buffer full.");
if (mHostEntry.hostEnt.h_name == nsnull) return;
memcpy(mHostEntry.hostEnt.h_name, mInetHostInfo.name, len + 1);
// set aliases to nsnull
mHostEntry.hostEnt.h_aliases = (char **)BufAlloc(sizeof(char *), &mHostEntry.bufPtr, &mHostEntry.bufLen, sizeof(char *));
NS_ASSERTION(nsnull != mHostEntry.hostEnt.h_aliases,"nsHostEnt buffer full.");
if (mHostEntry.hostEnt.h_aliases == nsnull) return;
*mHostEntry.hostEnt.h_aliases = nsnull;
// fill in address type
mHostEntry.hostEnt.h_addrtype = PR_AF_INET6;
// set address length
mHostEntry.hostEnt.h_length = sizeof(PRIPv6Addr);
// count addresses and allocate storage for the pointers
for (count = 0; count < kMaxHostAddrs && mInetHostInfo.addrs[count] != nsnull; count++){}
count++; // for terminating null address
mHostEntry.hostEnt.h_addr_list = (char **)BufAlloc(count * sizeof(char *), &mHostEntry.bufPtr, &mHostEntry.bufLen, sizeof(char *));
NS_ASSERTION(nsnull != mHostEntry.hostEnt.h_addr_list,"nsHostEnt buffer full.");
if (mHostEntry.hostEnt.h_addr_list == nsnull) return;
// copy addresses one at a time
len = mHostEntry.hostEnt.h_length;
for (i = 0; i < kMaxHostAddrs && mInetHostInfo.addrs[i] != nsnull; i++)
{
mHostEntry.hostEnt.h_addr_list[i] = BufAlloc(len, &mHostEntry.bufPtr, &mHostEntry.bufLen, len);
NS_ASSERTION(nsnull != mHostEntry.hostEnt.h_addr_list[i],"nsHostEnt buffer full.");
if (mHostEntry.hostEnt.h_addr_list[i] == nsnull) {
if (i > 0) break; // if we have at least one address, then don't fail completely
return;
}
PR_ConvertIPv4AddrToIPv6(mInetHostInfo.addrs[i],
(PRIPv6Addr *)mHostEntry.hostEnt.h_addr_list[i]);
}
mHostEntry.hostEnt.h_addr_list[i] = nsnull;
// hey! we made it.
mStatus = NS_OK;
mState = LOOKUP_COMPLETE;
}
#endif
PRBool
nsDNSLookup::HostnameIsIPAddress()
{
// try converting hostname to an IP-Address
PRNetAddr *netAddr = (PRNetAddr*)nsMemory::Alloc(sizeof(PRNetAddr));
if (!netAddr) {
return PR_FALSE; // couldn't allocate memory
}
PRStatus status = PR_StringToNetAddr(mHostName, netAddr);
if (PR_SUCCESS != status) {
nsMemory::Free(netAddr);
return PR_FALSE;
}
// slam the IP in and move on.
mHostEntry.bufLen = PR_NETDB_BUF_SIZE;
PRUint32 hostNameLen = strlen(mHostName);
mHostEntry.hostEnt.h_name = (char*)BufAlloc(hostNameLen + 1,
(char**)&mHostEntry.bufPtr,
&mHostEntry.bufLen,
0);
memcpy(mHostEntry.hostEnt.h_name, mHostName, hostNameLen + 1);
mHostEntry.hostEnt.h_aliases = (char**)BufAlloc(1 * sizeof(char*),
(char**)&mHostEntry.bufPtr,
&mHostEntry.bufLen,
sizeof(char **));
mHostEntry.hostEnt.h_aliases[0] = nsnull;
mHostEntry.hostEnt.h_addrtype = PR_AF_INET6;
mHostEntry.hostEnt.h_length = sizeof(PRIPv6Addr);
mHostEntry.hostEnt.h_addr_list = (char**)BufAlloc(2 * sizeof(char*),
(char**)&mHostEntry.bufPtr,
&mHostEntry.bufLen,
sizeof(char **));
mHostEntry.hostEnt.h_addr_list[0] = (char*)BufAlloc(mHostEntry.hostEnt.h_length,
(char**)&mHostEntry.bufPtr,
&mHostEntry.bufLen,
0);
if (PR_NetAddrFamily(netAddr) == PR_AF_INET6) {
memcpy(mHostEntry.hostEnt.h_addr_list[0], &netAddr->ipv6.ip, mHostEntry.hostEnt.h_length);
} else {
PR_ConvertIPv4AddrToIPv6(netAddr->inet.ip,
(PRIPv6Addr *)mHostEntry.hostEnt.h_addr_list[0]);
}
mHostEntry.hostEnt.h_addr_list[1] = nsnull;
MarkComplete(NS_OK);
MarkNotCacheable(); // no need to cache ip address strings
nsMemory::Free(netAddr);
return PR_TRUE;
}
nsresult
nsDNSLookup::InitiateLookup()
{
NS_ASSERTION(PR_CLIST_IS_EMPTY(this), "lookup being initiated while on queue");
if (HostnameIsIPAddress()) return NS_OK;
#if defined(XP_MAC)
nsDNSService::gService->EnqueuePendingQ(this);
OSErr err = OTInetStringToAddress(nsDNSService::gService->mServiceRef, mHostName, &mInetHostInfo);
if (err == noErr) return NS_OK;
PR_REMOVE_AND_INIT_LINK(this);
return NS_ERROR_UNEXPECTED;
#elif defined(XP_WIN)
mMsgID = nsDNSService::gService->AllocMsgID();
if (mMsgID == 0) return NS_ERROR_UNEXPECTED;
nsDNSService::gService->EnqueuePendingQ(this);
mLookupHandle = WSAAsyncGetHostByName(nsDNSService::gService->mDNSWindow, mMsgID,
mHostName, (char *)&mHostEntry.hostEnt, PR_NETDB_BUF_SIZE);
// check for error conditions
if (mLookupHandle == nsnull) {
NS_WARNING("WSAAsyncGetHostByName return null\n");
PR_REMOVE_AND_INIT_LINK(this);
return NS_ERROR_UNEXPECTED; // or call WSAGetLastError() for details;
// While we would like to use mLookupHandle to allow for canceling the request in
// the future, there is a bug with winsock2 on win95 that causes WSAAsyncGetHostByName
// to always return the same value. We have worked around this problem by using the
// msgID to identify which lookup has completed, rather than the handle, however, we
// still need to identify when this is a problem (by comparing the return values of
// the first two calls made to WSAAsyncGetHostByName), to avoid using the handle on
// those systems. For more info, see bug 23709.
}
return NS_OK;
#elif defined(XP_UNIX) || defined(XP_BEOS) || defined(XP_OS2)
// Add to pending lookup queue
nsDNSService::gService->EnqueuePendingQ(this);
return NS_OK;
#endif
}
void
nsDNSLookup::MarkComplete(nsresult status)
{
mStatus = status;
mState = LOOKUP_COMPLETE;
if (NS_FAILED(status))
MarkNotCacheable();
}
nsresult
nsDNSLookup::EnqueueRequest(nsDNSRequest * request)
{
// mDNSServiceLock must be held, this method releases & reaquires it.
// must guarantee lookup will not be deallocated for duration of method
nsresult rv;
nsDNSService::Unlock(); // can't hold locks during callback
rv = request->FireStart();
nsDNSService::Lock();
if (NS_FAILED(rv)) return rv;
PR_APPEND_LINK(request, &mRequestQ);
NS_ADDREF(request); // keep request until processed
if (mState == LOOKUP_NEW) {
// we need to kick off the lookup
mState = LOOKUP_PENDING;
rv = InitiateLookup();
if (NS_FAILED(rv)) MarkComplete(rv);
}
return NS_OK;
}
void
nsDNSLookup::ProcessRequests()
{
// mDNSServiceLock must be held, this method releases & reaquires it.
// must guarantee lookup will not be deallocated for duration of method
++mProcessingRequests;
nsDNSRequest * request = (nsDNSRequest *)PR_LIST_HEAD(&mRequestQ);
while (request != &mRequestQ) {
PR_REMOVE_AND_INIT_LINK(request);
nsDNSService::Unlock(); // can't hold lock during callback
nsresult rv = request->FireStop(mStatus);
NS_ASSERTION(NS_SUCCEEDED(rv), "request->FireStop() failed.");
NS_RELEASE(request);
nsDNSService::Lock();
request = (nsDNSRequest *)PR_LIST_HEAD(&mRequestQ);
}
--mProcessingRequests;
}
PRBool
nsDNSLookup::IsExpired()
{
#ifdef xDEBUG
char buf[256];
PRExplodedTime et;
PR_ExplodeTime(mExpires, PR_LocalTimeParameters, &et);
PR_FormatTimeUSEnglish(buf, sizeof(buf), "%c", &et);
fprintf(stderr, "\nDNS %s expires %s\n", mHostName, buf);
PR_ExplodeTime(nsTime(), PR_LocalTimeParameters, &et);
PR_FormatTimeUSEnglish(buf, sizeof(buf), "%c", &et);
fprintf(stderr, "now %s ==> %s\n", buf,
mExpires < nsTime()
? "expired" : "valid");
fflush(stderr);
#endif
return mExpires < nsTime();
}
#if defined(XP_UNIX) || defined(XP_BEOS) || defined(XP_OS2)
PRStatus
nsDNSLookup::DoSyncLookupInternal()
{
PRStatus status;
// must not hold the service lock while resolving, otherwise we could
// end up locking up the DNS service and anyone who accesses it until
// this function returns, which could be a while if the DNS server is
// unreachable. XXX is this thread-safe?
nsDNSService::Unlock();
status = PR_GetIPNodeByName(mHostName,
PR_AF_INET6,
PR_AI_DEFAULT,
mHostEntry.buffer,
PR_NETDB_BUF_SIZE,
&(mHostEntry.hostEnt));
nsDNSService::Lock();
return status;
}
void
nsDNSLookup::DoSyncLookup()
{
PRStatus status = DoSyncLookupInternal();
if (PR_SUCCESS != status) {
if (nsDNSService::Reset());
status = DoSyncLookupInternal();
}
MarkComplete(PR_SUCCESS == status ? NS_OK : NS_ERROR_UNKNOWN_HOST);
}
#endif /* XP_UNIX || XP_BEOS || XP_OS2*/
/******************************************************************************
* nsDNSService
*****************************************************************************/
#ifdef XP_MAC
#pragma mark -
#pragma mark nsDNSService
#endif
nsDNSService * nsDNSService::gService = nsnull;
PRBool nsDNSService::gNeedLateInitialization = PR_FALSE;
PLDHashTableOps nsDNSService::gHashTableOps =
{
PL_DHashAllocTable,
PL_DHashFreeTable,
GetKey,
PL_DHashStringKey,
MatchEntry,
MoveEntry,
ClearEntry,
PL_DHashFinalizeStub
};
nsDNSService::nsDNSService()
: mPrefService(nsnull)
, mDNSServiceLock(nsnull)
, mDNSCondVar(nsnull)
, mEvictionQCount(0)
, mMaxCachedLookups(32)
, mExpirationInterval(5*60) // 300 seconds (5 minutes)
, mMyIPAddress(0)
, mState(DNS_NOT_INITIALIZED)
#if defined(XP_MAC)
, mServiceRef(nsnull)
#endif
#if defined(XP_WIN)
, mDNSWindow(nsnull)
#endif
#ifdef DNS_TIMING
, mCount(0)
, mTimes(0)
, mSquaredTimes(0)
, mOut(nsnull)
#endif
{
NS_INIT_REFCNT();
NS_ASSERTION(gService==nsnull,"multiple nsDNSServices allocated!");
gService = this;
PR_INIT_CLIST(&mPendingQ);
PR_INIT_CLIST(&mEvictionQ);
// Let us say that the most times a reset can happen is one time every second.
mResetMaxInterval = PR_SecondsToInterval(1);
mLastReset = PR_IntervalNow();
#if defined(XP_MAC)
gNeedLateInitialization = PR_TRUE;
#if TARGET_CARBON
mClientContext = nsnull;
#endif /* TARGET_CARBON */
#endif /* defined(XP_MAC) */
#if defined(XP_WIN)
// initialize bit vector for allocating message IDs.
int i;
for (i=0; i<4; i++)
mMsgIDBitVector[i] = 0;
#endif
#ifdef DNS_TIMING
if (getenv("DNS_TIMING")) {
mOut = fopen("dns-timing.txt", "a");
if (mOut) {
PRTime now = PR_Now();
PRExplodedTime time;
PR_ExplodeTime(now, PR_LocalTimeParameters, &time);
char buf[128];
PR_FormatTimeUSEnglish(buf, sizeof(buf), "%c", &time);
fprintf(mOut, "############### DNS starting new run: %s\n", buf);
}
}
#endif
}
NS_IMETHODIMP
nsDNSService::Init()
{
nsresult rv = NS_OK;
#if defined(XP_WIN)
PRStatus status = PR_SUCCESS;
#endif
if (mState == DNS_OFFLINE) {
mState = DNS_ONLINE;
return NS_OK;
}
NS_ASSERTION(mDNSServiceLock == nsnull, "nsDNSService not shut down");
if (mDNSServiceLock) return NS_ERROR_ALREADY_INITIALIZED;
#if defined(XP_MAC)
OSStatus errOT = INIT_OPEN_TRANSPORT();
NS_ASSERTION(errOT == kOTNoError, "InitOpenTransport failed.");
if (errOT != kOTNoError) return NS_ERROR_UNEXPECTED;
nsDnsServiceNotifierRoutineUPP = NewOTNotifyUPP(nsDnsServiceNotifierRoutine);
if (nsDnsServiceNotifierRoutineUPP == nsnull)
return NS_ERROR_OUT_OF_MEMORY;
#endif
PRBool initialized = PL_DHashTableInit(&mHashTable, &gHashTableOps, nsnull,
sizeof(DNSHashTableEntry), 512);
if (!initialized) return NS_ERROR_OUT_OF_MEMORY;
mDNSServiceLock = PR_NewLock();
if (mDNSServiceLock == nsnull) return NS_ERROR_OUT_OF_MEMORY;
#if defined(XP_UNIX) || defined(XP_WIN) || defined(XP_BEOS) || defined(XP_OS2)
mDNSCondVar = PR_NewCondVar(mDNSServiceLock);
if (!mDNSCondVar) {
rv = NS_ERROR_OUT_OF_MEMORY;
goto error_exit;
}
#endif
#if defined(XP_WIN)
{
nsAutoLock dnsLock(mDNSServiceLock);
#endif
// create DNS thread
NS_ASSERTION(mThread == nsnull, "nsDNSService not shut down");
rv = NS_NewThread(getter_AddRefs(mThread), this, 0, PR_JOINABLE_THREAD);
NS_ASSERTION(NS_SUCCEEDED(rv), "NS_NewThread failed.");
if (NS_FAILED(rv)) goto error_exit;
#if defined(XP_WIN)
// sync with DNS thread to allow it to create the DNS window
while (!mDNSWindow) {
status = PR_WaitCondVar(mDNSCondVar, PR_INTERVAL_NO_TIMEOUT);
NS_ASSERTION(status == PR_SUCCESS, "PR_WaitCondVar failed.");
}
#endif
rv = InstallPrefObserver();
if (NS_FAILED(rv)) return rv;
mState = DNS_ONLINE;
return NS_OK;
#if defined(XP_WIN)
}
#endif
error_exit:
#if defined(XP_UNIX) || defined(XP_WIN) || defined(XP_BEOS) || defined(XP_OS2)
if (mDNSCondVar) PR_DestroyCondVar(mDNSCondVar);
mDNSCondVar = nsnull;
#endif
if (mDNSServiceLock) PR_DestroyLock(mDNSServiceLock);
mDNSServiceLock = nsnull;
return rv;
}
nsresult
nsDNSService::LateInit()
{
#if defined(XP_MAC)
// create Open Transport Service Provider for DNS Lookups
OSStatus errOT;
if (mServiceRef) {
NS_ASSERTION(gNeedLateInitialization == PR_TRUE, "dns badly initialized");
return (gNeedLateInitialization == PR_TRUE) ? NS_OK : NS_ERROR_UNEXPECTED;
}
mServiceRef = OT_OPEN_INTERNET_SERVICES(kDefaultInternetServicesPath, NULL, &errOT);
NS_ASSERTION((mServiceRef != nsnull) && (errOT == kOTNoError), "error opening OT service.");
if (errOT != kOTNoError) return NS_ERROR_UNEXPECTED; // no network -- oh well
// Install notify function for DNR Address To String completion
errOT = OTInstallNotifier(mServiceRef, nsDnsServiceNotifierRoutineUPP, this);
NS_ASSERTION(errOT == kOTNoError, "error installing dns notification routine.");
// Put us into async mode
if (errOT == kOTNoError) {
errOT = OTSetAsynchronous(mServiceRef);
NS_ASSERTION(errOT == kOTNoError, "error setting service to async mode.");
}
// if either of the two previous calls failed then dealloc service ref and fail
if (errOT != kOTNoError) {
OSStatus status = OTCloseProvider((ProviderRef)mServiceRef);
mServiceRef = nsnull;
return NS_ERROR_UNEXPECTED;
}
#endif
gNeedLateInitialization = PR_FALSE;
return NS_OK;
}
nsDNSService::~nsDNSService()
{
nsresult rv = ShutdownInternal();
NS_ASSERTION(NS_SUCCEEDED(rv), "DNS shutdown failed");
NS_ASSERTION(mThread == nsnull, "DNS shutdown failed");
NS_ASSERTION(mDNSServiceLock == nsnull, "DNS shutdown failed");
NS_ASSERTION(PR_CLIST_IS_EMPTY(&mPendingQ), "didn't clean up lookups");
NS_ASSERTION(PR_CLIST_IS_EMPTY(&mEvictionQ), "didn't clean up lookups");
#if defined(XP_UNIX) || defined(XP_WIN) || defined(XP_BEOS) || defined(XP_OS2)
NS_ASSERTION(mDNSCondVar == nsnull, "DNS condition variable not destroyed");
#endif
NS_ASSERTION(gService==this,"multiple nsDNSServices allocated.");
gService = nsnull;
#ifdef DNS_TIMING
if (mOut) {
double mean;
double stddev;
NS_MeanAndStdDev(mCount, mTimes, mSquaredTimes, &mean, &stddev);
fprintf(mOut, "DNS lookup time: %.2f +/- %.2f us (%d lookups)\n",
mean, stddev, (PRInt32)mCount);
fclose(mOut);
mOut = nsnull;
}
#endif
CRTFREEIF(mMyIPAddress);
}
NS_IMPL_THREADSAFE_ISUPPORTS3(nsDNSService, nsIDNSService, nsIRunnable, nsIObserver);
NS_METHOD
nsDNSService::Create(nsISupports* aOuter, const nsIID& aIID, void* *aResult)
{
nsresult rv;
if (aOuter != nsnull)
return NS_ERROR_NO_AGGREGATION;
nsDNSService* dnsService = new nsDNSService();
if (dnsService == nsnull)
return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF(dnsService);
rv = dnsService->Init();
if (NS_SUCCEEDED(rv)) {
rv = dnsService->QueryInterface(aIID, aResult);
}
NS_RELEASE(dnsService);
return rv;
}
void
nsDNSService::Lock()
{
NS_ASSERTION(gService, "nsDNSService::Lock: No DNS Service");
if (gService && gService->mDNSServiceLock) PR_Lock(gService->mDNSServiceLock);
}
void
nsDNSService::Unlock()
{
if (gService && gService->mDNSServiceLock) {
PRStatus status = PR_Unlock(gService->mDNSServiceLock);
NS_ASSERTION(status == PR_SUCCESS, "incorrect unlock of mDNSServiceLock");
}
}
PRBool
nsDNSService::Reset()
{
#ifdef HAVE_RES_NINIT
// On platforms where res_ninit() is available and supported
// by Mozilla, we shall attempt to reinitialize the dns
// resolver. Since this method may be called many times in
// a row, we shall restrict calling into res_init no more than
// once every mResetMaxInterval.
//
// For the linux multithreaded resolver discussion see:
// http://sources.redhat.com/ml/libc-hacker/2000-07/msg00487.html
if (gService &&
PR_IntervalNow() - gService->mLastReset >= gService->mResetMaxInterval) {
(void) res_ninit(&_res);
gService->mLastReset = PR_IntervalNow();
return PR_TRUE;
}
#endif
return PR_FALSE;
}
PRInt32
nsDNSService::ExpirationInterval()
{
if (gService) return gService->mExpirationInterval;
return 0;
}
#define NETWORK_DNS_CACHE_ENTRIES "network.dnsCacheEntries"
#define NETWORK_DNS_CACHE_EXPIRATION "network.dnsCacheExpiration"
#define NETWORK_ENABLEIDN "network.enableIDN"
nsresult
nsDNSService::InstallPrefObserver()
{
nsresult rv;
nsCOMPtr<nsIPrefService> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
if (NS_FAILED(rv)) return rv;
// save for later
mPrefService = getter_AddRefs( NS_GetWeakReference(prefs));
nsCOMPtr<nsIPrefBranchInternal> prefInternal = do_QueryInterface(prefs, &rv);
if (NS_FAILED(rv)) return rv;
rv = prefInternal->AddObserver(NETWORK_DNS_CACHE_ENTRIES, this, PR_FALSE);
if (NS_FAILED(rv)) return rv;
rv = prefInternal->AddObserver(NETWORK_DNS_CACHE_EXPIRATION, this, PR_FALSE);
if (NS_FAILED(rv)) return rv;
rv = prefInternal->AddObserver(NETWORK_ENABLEIDN, this, PR_FALSE);
if (NS_FAILED(rv)) return rv;
// get initial values (if any)
nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(prefs, &rv);
if (NS_FAILED(rv)) return rv;
PRInt32 prefVal = 0;
rv = prefBranch->GetIntPref(NETWORK_DNS_CACHE_ENTRIES, &prefVal);
if (NS_SUCCEEDED(rv)) mMaxCachedLookups = prefVal;
rv = prefBranch->GetIntPref(NETWORK_DNS_CACHE_EXPIRATION, &prefVal);
if (NS_SUCCEEDED(rv)) mExpirationInterval = prefVal;
PRBool enableIDN = PR_FALSE;
rv = prefBranch->GetBoolPref(NETWORK_ENABLEIDN, &enableIDN);
NS_ASSERTION(NS_SUCCEEDED(rv),"GetBoolPref failed!");
if (enableIDN)
mIDNConverter = do_GetService(NS_IDNSERVICE_CONTRACTID, &rv);
return NS_OK;
}
nsresult
nsDNSService::RemovePrefObserver()
{
nsresult rv, rv2;
nsCOMPtr<nsIPrefService> prefs = do_QueryReferent(mPrefService);
if (!prefs) return NS_OK; // no pref service, no need to unregister
nsCOMPtr<nsIPrefBranchInternal> prefInternal = do_QueryInterface(prefs, &rv);
if (NS_FAILED(rv)) return rv;
rv = prefInternal->RemoveObserver(NETWORK_ENABLEIDN, this);
if (NS_FAILED(rv)) return rv;
rv = prefInternal->RemoveObserver(NETWORK_DNS_CACHE_ENTRIES, this);
rv2 = prefInternal->RemoveObserver(NETWORK_DNS_CACHE_EXPIRATION, this);
return NS_FAILED(rv) ? rv : rv2;
}
NS_IMETHODIMP
nsDNSService::Observe(nsISupports * subject,
const char * topic,
const PRUnichar * data)
{
nsresult rv = NS_OK;
if (nsCRT::strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, topic))
return NS_OK;
nsCOMPtr<nsIPrefBranch> prefs = do_QueryInterface(subject, &rv);
if (NS_FAILED(rv)) return rv;
// which preference changed?
if (!nsCRT::strcmp(NETWORK_DNS_CACHE_ENTRIES, NS_ConvertUCS2toUTF8(data).get())) {
rv = prefs->GetIntPref(NETWORK_DNS_CACHE_ENTRIES, &mMaxCachedLookups);
if (mMaxCachedLookups < 0)
mMaxCachedLookups = 0;
} else if (!nsCRT::strcmp(NETWORK_DNS_CACHE_EXPIRATION, NS_ConvertUCS2toUTF8(data).get())) {
rv = prefs->GetIntPref(NETWORK_DNS_CACHE_EXPIRATION, &mExpirationInterval);
if (mExpirationInterval < 0)
mExpirationInterval = 0;
}
else if (!nsCRT::strcmp(NETWORK_ENABLEIDN, NS_ConvertUCS2toUTF8(data).get())) {
PRBool enableIDN = PR_FALSE;
rv = prefs->GetBoolPref(NETWORK_ENABLEIDN, &enableIDN);
/* We need to acquire the DNS lock when clearing mIDNConverter because:
* 1) this method runs only on the main UI thread
* 2) users of mIDNConvert (nsDNSService::Lookup) runs on other
* threads inside the DNS service lock
* BUT we do not perform lock when setting mIDNConverter because
* pointer stores are atomic.
*/
if (enableIDN && !mIDNConverter) {
mIDNConverter = do_GetService(NS_IDNSERVICE_CONTRACTID, &rv);
NS_ASSERTION(NS_SUCCEEDED(rv),"idnSDK not installed");
}
else if (!enableIDN && mIDNConverter) {
nsAutoLock dnsLock(mDNSServiceLock);
mIDNConverter = nsnull;
}
}
return rv;
}
#if defined(XP_MAC)
NS_IMETHODIMP
nsDNSService::Run()
{
while (mState != DNS_SHUTTING_DOWN) {
// can't use a PRCondVar because we need to notify from an interrupt
PR_Mac_WaitForAsyncNotify(PR_INTERVAL_NO_TIMEOUT);
// check queue for completed DNS lookups
PR_Lock(mDNSServiceLock);
nsDNSLookup * lookup;
while ((lookup = DequeuePendingQ())) {
NS_ADDREF(lookup);
if (NS_SUCCEEDED(lookup->Status())) {
// convert InetHostInfo to nsHostEnt
lookup->ConvertHostEntry();
}
lookup->ProcessRequests();
if (lookup->IsNotCacheable()) {
EvictLookup(lookup);
} else {
AddToEvictionQ(lookup);
}
NS_RELEASE(lookup);
}
PR_Unlock(mDNSServiceLock);
}
return NS_OK;
}
#elif defined(XP_UNIX) || defined(XP_BEOS) || defined(XP_OS2)
NS_IMETHODIMP
nsDNSService::Run()
{
nsAutoLock dnsLock(mDNSServiceLock);
while (mState != DNS_SHUTTING_DOWN) {
nsDNSLookup * lookup = DequeuePendingQ();
if (lookup) {
// Got a request!!
NS_ADDREF(lookup); // keep the lookup while we process it
lookup->DoSyncLookup();
lookup->ProcessRequests();
if (lookup->IsNotCacheable()) {
EvictLookup(lookup);
} else {
AddToEvictionQ(lookup);
}
NS_RELEASE(lookup);
} else {
// Woken up without a request --> shutdown
NS_ASSERTION(mState == DNS_SHUTTING_DOWN, "bad DNS shutdown state");
break;
}
}
return NS_OK;
}
#elif defined(XP_WIN)
NS_IMETHODIMP
nsDNSService::Run()
{
WNDCLASS wc;
char * windowClass = "Mozilla:DNSWindowClass";
// register window class for DNS event receiver window
memset(&wc, 0, sizeof(wc));
wc.style = 0;
wc.lpfnWndProc = nsDNSEventProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = NULL;
wc.hIcon = NULL;
wc.hbrBackground = (HBRUSH) NULL;
wc.lpszMenuName = (LPCSTR) NULL;
wc.lpszClassName = windowClass;
RegisterClass(&wc);
// create DNS event receiver window
mDNSWindow = CreateWindow(windowClass, "Mozilla:DNSWindow",
0, 0, 0, 10, 10, NULL, NULL, NULL, NULL);
// sync with Create thread
Lock();
PRStatus status = PR_NotifyCondVar(mDNSCondVar);
NS_ASSERTION(status == PR_SUCCESS, "PR_NotifyCondVar failed.");
Unlock();
MSG msg;
while(GetMessage(&msg, mDNSWindow, 0, 0)) {
// no TranslateMessage() because we're not expecting input
DispatchMessage(&msg);
}
return NS_OK;
}
#else
NS_IMETHODIMP
nsDNSService::Run()
{
NS_NOTREACHED("platform requires async implementation");
return NS_ERROR_FAILURE;
}
#endif
/******************************************************************************
* nsIDNSService methods
*****************************************************************************/
NS_IMETHODIMP
nsDNSService::Lookup(const char* hostName,
nsIDNSListener* userListener,
nsISupports* userContext,
nsIRequest* *result)
{
nsresult rv;
nsDNSRequest * request = nsnull;
*result = nsnull;
if (!mDNSServiceLock || (mState != DNS_ONLINE)) return NS_ERROR_OFFLINE;
else {
nsAutoLock dnsLock(mDNSServiceLock);
if (gNeedLateInitialization) {
rv = LateInit();
if (NS_FAILED(rv)) return rv;
}
if (mThread == nsnull)
return NS_ERROR_OFFLINE;
nsDNSLookup *lookup = nsnull;
// IDN handling
if (mIDNConverter && !nsCRT::IsAscii(hostName)) {
nsXPIDLCString hostNameACE;
rv = mIDNConverter->ConvertUTF8toACE(hostName, getter_Copies(hostNameACE));
if (!hostNameACE.get()) return NS_ERROR_OUT_OF_MEMORY;
lookup = FindOrCreateLookup(hostNameACE);
}
// if it hasn't been created (no IDN converter / not an IDN hostName)
if (!lookup)
lookup = FindOrCreateLookup(hostName);
if (!lookup) return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF(lookup); // keep it around for life of this method.
request = new nsDNSRequest(lookup, userListener, userContext);
if (!request) {
rv = NS_ERROR_OUT_OF_MEMORY;
goto exit;
}
NS_ADDREF(request); // for caller
rv = lookup->EnqueueRequest(request); // releases and re-acquires dns lock
if (NS_FAILED(rv)) goto exit;
if (lookup->IsComplete()) {
lookup->ProcessRequests(); // releases and re-acquires dns lock
if (lookup->IsNotCacheable()) {
// non-cacheable lookups are released here.
EvictLookup(lookup);
} else {
#if 0
if (PR_CLIST_IS_EMPTY(lookup)) // if the lookup isn't in the eviction queue
AddToEvictionQ(lookup);
#endif
}
}
exit:
if (lookup->IsNew()) EvictLookup(lookup);
NS_RELEASE(lookup); // it's either on a queue, or we're done with it
}
if (NS_SUCCEEDED(rv)) *result = request;
else NS_IF_RELEASE(request);
return rv;
}
nsDNSLookup *
nsDNSService::FindOrCreateLookup(const char* hostName)
{
// find hostname in hashtable
PLDHashEntryHdr * hashEntry;
nsDNSLookup * lookup = nsnull;
hashEntry = PL_DHashTableOperate(&mHashTable, hostName, PL_DHASH_LOOKUP);
if (PL_DHASH_ENTRY_IS_BUSY(hashEntry)) {
lookup = ((DNSHashTableEntry *)hashEntry)->mLookup;
if (lookup->IsComplete() && lookup->IsExpired() && lookup->IsNotProcessing()) {
lookup->Reset();
PR_REMOVE_AND_INIT_LINK(lookup); // remove us from eviction queue
--mEvictionQCount;
}
return lookup;
}
// no lookup entry exists for this request
lookup = nsDNSLookup::Create(hostName);
if (lookup == nsnull) return nsnull;
// insert in hash table
hashEntry = PL_DHashTableOperate(&mHashTable, lookup->HostName(), PL_DHASH_ADD);
if (!hashEntry) {
NS_RELEASE(lookup);
return nsnull;
}
((DNSHashTableEntry *)hashEntry)->mLookup = lookup;
return lookup;
}
void
nsDNSService::EnqueuePendingQ(nsDNSLookup * lookup)
{
PR_APPEND_LINK(lookup, &mPendingQ);
#if defined(XP_UNIX) || defined(XP_BEOS) || defined(XP_OS2)
// Notify the worker thread that a request has been queued.
PRStatus status = PR_NotifyCondVar(mDNSCondVar);
NS_ASSERTION(status == PR_SUCCESS, "PR_NotifyCondVar failed.");
#endif
}
nsDNSLookup *
nsDNSService::DequeuePendingQ()
{
#if defined(XP_UNIX) || defined(XP_BEOS) || defined(XP_OS2)
// Wait for notification of a queued request, unless we're shutting down.
while (PR_CLIST_IS_EMPTY(&mPendingQ) && (mState != DNS_SHUTTING_DOWN)) {
PRStatus status = PR_WaitCondVar(mDNSCondVar, PR_INTERVAL_NO_TIMEOUT);
NS_ASSERTION(status == PR_SUCCESS, "PR_WaitCondVar failed.");
}
#endif
nsDNSLookup * lookup = (nsDNSLookup *)PR_LIST_HEAD(&mPendingQ);
#if defined(XP_MAC)
// dequeue only completed lookups
while (lookup != &mPendingQ) {
if (lookup->mStringToAddrComplete) break;
lookup = (nsDNSLookup *)PR_NEXT_LINK(lookup);
}
#endif
if (lookup == &mPendingQ) return nsnull;
PR_REMOVE_AND_INIT_LINK(lookup);
return lookup;
}
void
nsDNSService::EvictLookup(nsDNSLookup * lookup)
{
if (lookup->IsNotEvicted()) {
// remove from hashtable
lookup->MarkEvicted();
(void) PL_DHashTableOperate(&mHashTable, lookup->HostName(), PL_DHASH_REMOVE);
NS_RELEASE(lookup);
}
}
void
nsDNSService::AddToEvictionQ(nsDNSLookup * lookup)
{
PR_APPEND_LINK(lookup, &mEvictionQ);
++mEvictionQCount;
while (mEvictionQCount > mMaxCachedLookups) {
// evict oldest lookup
PRCList * elem = PR_LIST_HEAD(&mEvictionQ);
if (elem == &mEvictionQ) {
NS_ASSERTION(mEvictionQCount == 0, "dns eviction count out of sync");
mEvictionQCount = 0;
break;
}
nsDNSLookup * lookup = (nsDNSLookup *)elem;
PR_REMOVE_AND_INIT_LINK(lookup);
--mEvictionQCount;
EvictLookup(lookup);
}
}
void
nsDNSService::AbortLookups()
{
nsDNSLookup * lookup;
// walk pending queue, aborting lookups
lookup = (nsDNSLookup *)PR_LIST_HEAD(&mPendingQ);
while(lookup != &mPendingQ) {
PR_REMOVE_AND_INIT_LINK(lookup);
lookup->MarkComplete(NS_BINDING_ABORTED);
lookup->ProcessRequests();
EvictLookup(lookup);
lookup = (nsDNSLookup *)PR_LIST_HEAD(&mPendingQ);
}
// walk pending queue, aborting lookups
lookup = (nsDNSLookup *)PR_LIST_HEAD(&mEvictionQ);
while(lookup != &mEvictionQ) {
PR_REMOVE_AND_INIT_LINK(lookup);
EvictLookup(lookup);
--mEvictionQCount;
lookup = (nsDNSLookup *)PR_LIST_HEAD(&mEvictionQ);
}
NS_ASSERTION(mEvictionQCount == 0, "bad eviction queue count");
}
NS_IMETHODIMP
nsDNSService::Resolve(const char *i_hostname, char **o_ip)
{
if (!mDNSServiceLock || (mState != DNS_ONLINE)) return NS_ERROR_OFFLINE;
// Beware! This does not check for an IP address already in hostname.
NS_ENSURE_ARG_POINTER(o_ip);
*o_ip = 0;
NS_ENSURE_ARG_POINTER(i_hostname);
PRNetAddr netAddr;
PRIntn index = 0;
char ipBuffer[64];
// aquire lock to check hash table for existing lookup
// release lock before calling synchronous PR_GetHostByName()
{
nsAutoLock dnsLock(mDNSServiceLock);
PLDHashEntryHdr * hashEntry = PL_DHashTableOperate(&mHashTable, i_hostname, PL_DHASH_LOOKUP);
if (PL_DHASH_ENTRY_IS_BUSY(hashEntry)) {
nsDNSLookup * lookup = ((DNSHashTableEntry *)hashEntry)->mLookup;
if (lookup->IsComplete() && !lookup->IsExpired()) {
nsHostEnt * hep = lookup->HostEntry();
NS_ASSERTION(hep != nsnull, "null DNS Lookup HostEntry.\n");
if (hep) {
index = PR_EnumerateHostEnt(0, &hep->hostEnt, 0, &netAddr);
}
}
}
}
if (index == 0) {
PRHostEnt he;
char netdbbuf[PR_NETDB_BUF_SIZE];
if (PR_SUCCESS == PR_GetHostByName(i_hostname, netdbbuf, sizeof(netdbbuf), &he)) {
index = PR_EnumerateHostEnt(0, &he, 0, &netAddr);
}
}
if (index == 0) return NS_ERROR_FAILURE;
else {
PRStatus status = PR_NetAddrToString(&netAddr, ipBuffer, sizeof(ipBuffer));
if (status != PR_SUCCESS) return NS_ERROR_FAILURE;
}
return (*o_ip = nsCRT::strdup(ipBuffer)) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
}
NS_IMETHODIMP
nsDNSService::GetMyIPAddress(char * *o_ip)
{
NS_ENSURE_ARG_POINTER(o_ip);
static PRBool readOnce = PR_FALSE;
if (!readOnce || !mMyIPAddress)
{
readOnce = PR_TRUE;
char name[100];
if (PR_GetSystemInfo(PR_SI_HOSTNAME,
name,
sizeof(name)) == PR_SUCCESS)
{
char* hostname = nsCRT::strdup(name);
if (NS_SUCCEEDED(Resolve(hostname, &mMyIPAddress)))
{
CRTFREEIF(hostname);
}
else
{
CRTFREEIF(hostname);
return NS_ERROR_FAILURE;
}
}
}
*o_ip = nsCRT::strdup(mMyIPAddress);
return *o_ip ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
}
// a helper function to convert an IP address to long value.
// used by nsDNSService::IsInNet
static unsigned long convert_addr(const char* ip)
{
char *p, *q, *buf = 0;
int i;
unsigned char b[4];
unsigned long addr = 0L;
p = buf = PL_strdup(ip);
if (ip && p)
{
for (i=0; p && i<4 ; ++i)
{
q = PL_strchr(p, '.');
if (q)
*q = '\0';
b[i] = atoi(p) & 0xff;
if (q)
p = q+1;
}
addr = (((unsigned long)b[0] << 24) |
((unsigned long)b[1] << 16) |
((unsigned long)b[2] << 8) |
((unsigned long)b[3]));
PL_strfree(buf);
}
return htonl(addr);
};
NS_IMETHODIMP
nsDNSService::IsInNet(const char * ipaddr,
const char * pattern,
const char * maskstr,
PRBool * o_Result)
{
// Note -- should check that ipaddr is truly digits only!
// TODO
NS_ENSURE_ARG_POINTER(o_Result);
NS_ENSURE_ARG_POINTER(ipaddr && pattern && maskstr);
*o_Result = PR_FALSE;
unsigned long host = convert_addr(ipaddr);
unsigned long pat = convert_addr(pattern);
unsigned long mask = convert_addr(maskstr);
*o_Result = ((mask & host) == (mask & pat));
return NS_OK;
}
NS_IMETHODIMP
nsDNSService::Shutdown()
{
mState = DNS_OFFLINE;
return NS_OK;
}
nsresult
nsDNSService::ShutdownInternal()
{
nsresult rv = NS_OK;
if (mThread == nsnull) return rv;
if (!mDNSServiceLock) // already shutdown or not init'ed as yet.
return NS_ERROR_NOT_AVAILABLE;
PR_Lock(mDNSServiceLock);
mState = DNS_SHUTTING_DOWN;
#if defined(XP_MAC)
// let's shutdown Open Transport so outstanding lookups won't complete while we're cleaning them up
if (mServiceRef) {
(void) OTCloseProvider((ProviderRef)mServiceRef);
mServiceRef = nsnull;
gNeedLateInitialization = PR_TRUE;
}
CLOSE_OPEN_TRANSPORT(); // terminate routine should check flag and do this if Shutdown() is bypassed somehow
DisposeOTNotifyUPP(nsDnsServiceNotifierRoutineUPP);
PRThread* dnsServiceThread;
rv = mThread->GetPRThread(&dnsServiceThread);
if (dnsServiceThread)
PR_Mac_PostAsyncNotify(dnsServiceThread);
#elif defined(XP_UNIX) || defined(XP_BEOS) || defined(XP_OS2)
PRStatus status = PR_NotifyCondVar(mDNSCondVar);
NS_ASSERTION(status == PR_SUCCESS, "unable to notify dns cond var");
#elif defined(XP_WIN)
SendMessage(mDNSWindow, WM_DNS_SHUTDOWN, 0, 0);
#endif
PR_Unlock(mDNSServiceLock); // so dns thread can aquire it.
rv = mThread->Join(); // wait for dns thread to quit
NS_ASSERTION(NS_SUCCEEDED(rv), "dns thread join failed in shutdown.");
// clean up outstanding lookups
// - wait until DNS thread is gone so we don't collide while calling requests
PR_Lock(mDNSServiceLock);
AbortLookups();
(void) RemovePrefObserver();
// reset hashtable
// XXX assert hashtable is empty
PL_DHashTableFinish(&mHashTable);
// Have to break the cycle here, otherwise nsDNSService holds onto the thread
// and the thread holds onto the nsDNSService via its mRunnable
mThread = nsnull;
#if defined(XP_UNIX) || defined(XP_WIN) || defined(XP_BEOS) || defined(XP_OS2)
PR_DestroyCondVar(mDNSCondVar);
mDNSCondVar = nsnull;
#endif
PR_Unlock(mDNSServiceLock);
PR_DestroyLock(mDNSServiceLock);
mDNSServiceLock = nsnull;
mState = DNS_SHUTDOWN;
return rv;
}
/******************************************************************************
* XP_MAC methods
*****************************************************************************/
#if defined(XP_MAC)
pascal void
nsDnsServiceNotifierRoutine(void * contextPtr, OTEventCode code,
OTResult result, void * cookie)
{
OSStatus errOT;
nsDNSService * dnsService = (nsDNSService *)contextPtr;
nsDNSLookup * lookup = GET_LOOKUP_FROM_HOSTINFO(cookie);
PRThread * thread;
dnsService->mThread->GetPRThread(&thread);
switch (code) {
case T_DNRSTRINGTOADDRCOMPLETE:
// mark lookup complete and wake dns thread
nsresult rv = (result == kOTNoError) ? NS_OK : NS_ERROR_UNKNOWN_HOST;
if (NS_FAILED(rv)) lookup->MarkNotCacheable();
lookup->mStatus = rv;
lookup->mStringToAddrComplete = PR_TRUE;
if (thread) PR_Mac_PostAsyncNotify(thread);
break;
case kOTProviderWillClose:
errOT = OTSetSynchronous(dnsService->mServiceRef);
// fall through to case kOTProviderIsClosed
case kOTProviderIsClosed:
errOT = OTCloseProvider((ProviderRef)dnsService->mServiceRef);
dnsService->mServiceRef = nsnull;
nsDNSService::gNeedLateInitialization = PR_TRUE;
// set flag to iterate outstanding lookups and cancel
break;
#if DEBUG
default: // or else we don't handle the event
DebugStr("\punknown OT event in nsDnsServiceNotifier!");
#endif
}
}
#endif /* XP_MAC */
/******************************************************************************
* XP_WIN methods
*****************************************************************************/
#if defined(XP_WIN)
static LRESULT CALLBACK
nsDNSEventProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
LRESULT result = nsnull;
int error = nsnull;
if ((uMsg >= WM_USER) && (uMsg < WM_USER+128)) {
result = nsDNSService::gService->ProcessLookup(hWnd, uMsg, wParam, lParam);
}
else if (uMsg == WM_DNS_SHUTDOWN) {
// dispose DNS EventHandler Window
(void) DestroyWindow(nsDNSService::gService->mDNSWindow);
nsDNSService::gService->mDNSWindow = nsnull;
PostQuitMessage(0);
result = 0;
}
else {
result = DefWindowProc(hWnd, uMsg, wParam, lParam);
}
return result;
}
LRESULT
nsDNSService::ProcessLookup(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
nsAutoLock dnsLock(mDNSServiceLock);
// walk pending queue to find lookup for this (HANDLE)wParam
nsDNSLookup * lookup = (nsDNSLookup *)PR_LIST_HEAD(&mPendingQ);
while (lookup != &mPendingQ) {
if (lookup->mMsgID == uMsg) break;
lookup = (nsDNSLookup *)PR_NEXT_LINK(lookup);
}
if (lookup == &mPendingQ) return -1; // XXX right result?
NS_ADDREF(lookup); // keep the lookup while we process it
int error = WSAGETASYNCERROR(lParam);
lookup->MarkComplete(error ? NS_ERROR_UNKNOWN_HOST : NS_OK);
FreeMsgID(lookup->mMsgID);
PR_REMOVE_AND_INIT_LINK(lookup);
lookup->ProcessRequests();
if (lookup->IsNotCacheable()) {
EvictLookup(lookup);
} else {
AddToEvictionQ(lookup);
}
NS_RELEASE(lookup);
return error ? -1 : 0;
}
PRUint32
nsDNSService::AllocMsgID()
{
PRUint32 i = 0;
PRUint32 bit = 0;
PRUint32 element;
while (i < 4) {
if (~mMsgIDBitVector[i] != 0) // break if any free bits in this element
break;
i++;
}
if (i >= 4)
return 0;
element = ~mMsgIDBitVector[i]; // element contains free bits marked as 1
if ((element & 0xFFFF) == 0) { bit |= 16; element >>= 16; }
if ((element & 0x00FF) == 0) { bit |= 8; element >>= 8; }
if ((element & 0x000F) == 0) { bit |= 4; element >>= 4; }
if ((element & 0x0003) == 0) { bit |= 2; element >>= 2; }
if ((element & 0x0001) == 0) bit |= 1;
mMsgIDBitVector[i] |= (PRUint32)1 << bit;
return WM_USER + i * 32 + bit;
}
void
nsDNSService::FreeMsgID(PRUint32 msgID)
{
PRInt32 bit = msgID - WM_USER;
NS_ASSERTION((bit >= 0) && (bit < 128),"nsDNSService::FreeMsgID reports invalid msgID.");
mMsgIDBitVector[bit>>5] &= ~((PRUint32)1 << (bit & 0x1f));
}
#endif