1 /* vim:set ts=4 sw=4 sts=4 et cin: */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
15 * The Original Code is Mozilla.
17 * The Initial Developer of the Original Code is IBM Corporation.
18 * Portions created by IBM Corporation are Copyright (C) 2003
19 * IBM Corporation. All Rights Reserved.
24 * Alternatively, the contents of this file may be used under the terms of
25 * either the GNU General Public License Version 2 or later (the "GPL"), or
26 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 * in which case the provisions of the GPL or the LGPL are applicable instead
28 * of those above. If you wish to allow use of your version of this file only
29 * under the terms of either the GPL or the LGPL, and not to allow others to
30 * use your version of this file under the terms of the MPL, indicate your
31 * decision by deleting the provisions above and replace them with the notice
32 * and other provisions required by the GPL or the LGPL. If you do not delete
33 * the provisions above, a recipient may use your version of this file under
34 * the terms of any one of the MPL, the GPL or the LGPL.
36 * ***** END LICENSE BLOCK ***** */
38 #include "nsDNSService2.h"
39 #include "nsIDNSRecord.h"
40 #include "nsIDNSListener.h"
41 #include "nsICancelable.h"
42 #include "nsIProxyObjectManager.h"
43 #include "nsIPrefService.h"
44 #include "nsIPrefBranch.h"
45 #include "nsIPrefBranch2.h"
46 #include "nsIServiceManager.h"
47 #include "nsReadableUtils.h"
49 #include "nsAutoLock.h"
50 #include "nsAutoPtr.h"
52 #include "nsNetError.h"
53 #include "nsDNSPrefetch.h"
54 #include "nsIProtocolProxyService.h"
60 #include "nsIOService.h"
62 #include "mozilla/FunctionTimer.h"
64 static const char kPrefDnsCacheEntries
[] = "network.dnsCacheEntries";
65 static const char kPrefDnsCacheExpiration
[] = "network.dnsCacheExpiration";
66 static const char kPrefEnableIDN
[] = "network.enableIDN";
67 static const char kPrefIPv4OnlyDomains
[] = "network.dns.ipv4OnlyDomains";
68 static const char kPrefDisableIPv6
[] = "network.dns.disableIPv6";
69 static const char kPrefDisablePrefetch
[] = "network.dns.disablePrefetch";
71 //-----------------------------------------------------------------------------
73 class nsDNSRecord
: public nsIDNSRecord
79 nsDNSRecord(nsHostRecord
*hostRecord
)
80 : mHostRecord(hostRecord
)
86 virtual ~nsDNSRecord() {}
88 nsRefPtr
<nsHostRecord
> mHostRecord
;
90 int mIterGenCnt
; // the generation count of
91 // mHostRecord->addr_info when we
96 NS_IMPL_THREADSAFE_ISUPPORTS1(nsDNSRecord
, nsIDNSRecord
)
99 nsDNSRecord::GetCanonicalName(nsACString
&result
)
101 // this method should only be called if we have a CNAME
102 NS_ENSURE_TRUE(mHostRecord
->flags
& nsHostResolver::RES_CANON_NAME
,
103 NS_ERROR_NOT_AVAILABLE
);
105 // if the record is for an IP address literal, then the canonical
106 // host name is the IP address literal.
108 PR_Lock(mHostRecord
->addr_info_lock
);
109 if (mHostRecord
->addr_info
)
110 cname
= PR_GetCanonNameFromAddrInfo(mHostRecord
->addr_info
);
112 cname
= mHostRecord
->host
;
113 result
.Assign(cname
);
114 PR_Unlock(mHostRecord
->addr_info_lock
);
119 nsDNSRecord::GetNextAddr(PRUint16 port
, PRNetAddr
*addr
)
121 // not a programming error to poke the DNS record when it has no more
122 // entries. just fail without any debug warnings. this enables consumers
123 // to enumerate the DNS record without calling HasMore.
125 return NS_ERROR_NOT_AVAILABLE
;
127 PR_Lock(mHostRecord
->addr_info_lock
);
128 if (mHostRecord
->addr_info
) {
130 mIterGenCnt
= mHostRecord
->addr_info_gencnt
;
131 else if (mIterGenCnt
!= mHostRecord
->addr_info_gencnt
) {
132 // mHostRecord->addr_info has changed, so mIter is invalid.
133 // Restart the iteration. Alternatively, we could just fail.
135 mIterGenCnt
= mHostRecord
->addr_info_gencnt
;
137 mIter
= PR_EnumerateAddrInfo(mIter
, mHostRecord
->addr_info
, port
, addr
);
138 PR_Unlock(mHostRecord
->addr_info_lock
);
141 return NS_ERROR_NOT_AVAILABLE
;
145 PR_Unlock(mHostRecord
->addr_info_lock
);
146 if (!mHostRecord
->addr
) {
147 // Both mHostRecord->addr_info and mHostRecord->addr are null.
148 // This can happen if mHostRecord->addr_info expired and the
149 // attempt to reresolve it failed.
150 return NS_ERROR_NOT_AVAILABLE
;
152 memcpy(addr
, mHostRecord
->addr
, sizeof(PRNetAddr
));
154 port
= PR_htons(port
);
155 if (addr
->raw
.family
== PR_AF_INET
)
156 addr
->inet
.port
= port
;
158 addr
->ipv6
.port
= port
;
159 mDone
= PR_TRUE
; // no iterations
166 nsDNSRecord::GetNextAddrAsString(nsACString
&result
)
169 nsresult rv
= GetNextAddr(0, &addr
);
170 if (NS_FAILED(rv
)) return rv
;
173 if (PR_NetAddrToString(&addr
, buf
, sizeof(buf
)) == PR_SUCCESS
) {
177 NS_ERROR("PR_NetAddrToString failed unexpectedly");
178 return NS_ERROR_FAILURE
; // conversion failed for some reason
182 nsDNSRecord::HasMore(PRBool
*result
)
187 // unfortunately, NSPR does not provide a way for us to determine if
188 // there is another address other than to simply get the next address.
189 void *iterCopy
= mIter
;
191 *result
= NS_SUCCEEDED(GetNextAddr(0, &addr
));
192 mIter
= iterCopy
; // backup iterator
199 nsDNSRecord::Rewind()
207 //-----------------------------------------------------------------------------
209 class nsDNSAsyncRequest
: public nsResolveHostCallback
210 , public nsICancelable
214 NS_DECL_NSICANCELABLE
216 nsDNSAsyncRequest(nsHostResolver
*res
,
217 const nsACString
&host
,
218 nsIDNSListener
*listener
,
223 , mListener(listener
)
226 ~nsDNSAsyncRequest() {}
228 void OnLookupComplete(nsHostResolver
*, nsHostRecord
*, nsresult
);
230 nsRefPtr
<nsHostResolver
> mResolver
;
231 nsCString mHost
; // hostname we're resolving
232 nsCOMPtr
<nsIDNSListener
> mListener
;
238 nsDNSAsyncRequest::OnLookupComplete(nsHostResolver
*resolver
,
239 nsHostRecord
*hostRecord
,
242 // need to have an owning ref when we issue the callback to enable
243 // the caller to be able to addref/release multiple times without
244 // destroying the record prematurely.
245 nsCOMPtr
<nsIDNSRecord
> rec
;
246 if (NS_SUCCEEDED(status
)) {
247 NS_ASSERTION(hostRecord
, "no host record");
248 rec
= new nsDNSRecord(hostRecord
);
250 status
= NS_ERROR_OUT_OF_MEMORY
;
253 mListener
->OnLookupComplete(this, rec
, status
);
256 // release the reference to ourselves that was added before we were
257 // handed off to the host resolver.
261 NS_IMPL_THREADSAFE_ISUPPORTS1(nsDNSAsyncRequest
, nsICancelable
)
264 nsDNSAsyncRequest::Cancel(nsresult reason
)
266 NS_ENSURE_ARG(NS_FAILED(reason
));
267 mResolver
->DetachCallback(mHost
.get(), mFlags
, mAF
, this, reason
);
271 //-----------------------------------------------------------------------------
273 class nsDNSSyncRequest
: public nsResolveHostCallback
276 nsDNSSyncRequest(PRMonitor
*mon
)
280 virtual ~nsDNSSyncRequest() {}
282 void OnLookupComplete(nsHostResolver
*, nsHostRecord
*, nsresult
);
286 nsRefPtr
<nsHostRecord
> mHostRecord
;
293 nsDNSSyncRequest::OnLookupComplete(nsHostResolver
*resolver
,
294 nsHostRecord
*hostRecord
,
297 // store results, and wake up nsDNSService::Resolve to process results.
298 PR_EnterMonitor(mMonitor
);
301 mHostRecord
= hostRecord
;
303 PR_ExitMonitor(mMonitor
);
306 //-----------------------------------------------------------------------------
308 nsDNSService::nsDNSService()
313 nsDNSService::~nsDNSService()
316 PR_DestroyLock(mLock
);
319 NS_IMPL_THREADSAFE_ISUPPORTS3(nsDNSService
, nsIDNSService
, nsPIDNSService
,
327 NS_ENSURE_TRUE(!mResolver
, NS_ERROR_ALREADY_INITIALIZED
);
329 PRBool firstTime
= (mLock
== nsnull
);
332 PRUint32 maxCacheEntries
= 400;
333 PRUint32 maxCacheLifetime
= 3; // minutes
334 PRBool enableIDN
= PR_TRUE
;
335 PRBool disableIPv6
= PR_FALSE
;
336 PRBool disablePrefetch
= PR_FALSE
;
337 int proxyType
= nsIProtocolProxyService::PROXYCONFIG_DIRECT
;
339 nsAdoptingCString ipv4OnlyDomains
;
342 nsCOMPtr
<nsIPrefBranch2
> prefs
= do_GetService(NS_PREFSERVICE_CONTRACTID
);
345 if (NS_SUCCEEDED(prefs
->GetIntPref(kPrefDnsCacheEntries
, &val
)))
346 maxCacheEntries
= (PRUint32
) val
;
347 if (NS_SUCCEEDED(prefs
->GetIntPref(kPrefDnsCacheExpiration
, &val
)))
348 maxCacheLifetime
= val
/ 60; // convert from seconds to minutes
350 // ASSUMPTION: pref branch does not modify out params on failure
351 prefs
->GetBoolPref(kPrefEnableIDN
, &enableIDN
);
352 prefs
->GetBoolPref(kPrefDisableIPv6
, &disableIPv6
);
353 prefs
->GetCharPref(kPrefIPv4OnlyDomains
, getter_Copies(ipv4OnlyDomains
));
354 prefs
->GetBoolPref(kPrefDisablePrefetch
, &disablePrefetch
);
356 // If a manual proxy is in use, disable prefetch implicitly
357 prefs
->GetIntPref("network.proxy.type", &proxyType
);
361 mLock
= PR_NewLock();
363 return NS_ERROR_OUT_OF_MEMORY
;
365 // register as prefs observer
367 prefs
->AddObserver(kPrefDnsCacheEntries
, this, PR_FALSE
);
368 prefs
->AddObserver(kPrefDnsCacheExpiration
, this, PR_FALSE
);
369 prefs
->AddObserver(kPrefEnableIDN
, this, PR_FALSE
);
370 prefs
->AddObserver(kPrefIPv4OnlyDomains
, this, PR_FALSE
);
371 prefs
->AddObserver(kPrefDisableIPv6
, this, PR_FALSE
);
372 prefs
->AddObserver(kPrefDisablePrefetch
, this, PR_FALSE
);
374 // Monitor these to see if there is a change in proxy configuration
375 // If a manual proxy is in use, disable prefetch implicitly
376 prefs
->AddObserver("network.proxy.type", this, PR_FALSE
);
380 // we have to null out mIDN since we might be getting re-initialized
381 // as a result of a pref change.
382 nsCOMPtr
<nsIIDNService
> idn
;
384 idn
= do_GetService(NS_IDNSERVICE_CONTRACTID
);
386 nsDNSPrefetch::Initialize(this);
388 // Don't initialize the resolver if we're in offline mode.
389 // Later on, the IO service will reinitialize us when going online.
390 if (gIOService
->IsOffline() && !gIOService
->IsComingOnline())
393 nsRefPtr
<nsHostResolver
> res
;
394 nsresult rv
= nsHostResolver::Create(maxCacheEntries
,
396 getter_AddRefs(res
));
397 if (NS_SUCCEEDED(rv
)) {
398 // now, set all of our member variables while holding the lock
399 nsAutoLock
lock(mLock
);
402 mIPv4OnlyDomains
= ipv4OnlyDomains
; // exchanges buffer ownership
403 mDisableIPv6
= disableIPv6
;
405 // Disable prefetching either by explicit preference or if a manual proxy is configured
406 mDisablePrefetch
= disablePrefetch
|| (proxyType
== nsIProtocolProxyService::PROXYCONFIG_MANUAL
);
412 nsDNSService::Shutdown()
414 nsRefPtr
<nsHostResolver
> res
;
416 nsAutoLock
lock(mLock
);
426 nsDNSService::AsyncResolve(const nsACString
&hostname
,
428 nsIDNSListener
*listener
,
429 nsIEventTarget
*target
,
430 nsICancelable
**result
)
432 // grab reference to global host resolver and IDN service. beware
433 // simultaneous shutdown!!
434 nsRefPtr
<nsHostResolver
> res
;
435 nsCOMPtr
<nsIIDNService
> idn
;
437 nsAutoLock
lock(mLock
);
439 if (mDisablePrefetch
&& (flags
& RESOLVE_SPECULATE
))
440 return NS_ERROR_DNS_LOOKUP_QUEUE_FULL
;
446 return NS_ERROR_OFFLINE
;
448 const nsACString
*hostPtr
= &hostname
;
451 nsCAutoString hostACE
;
452 if (idn
&& !IsASCII(hostname
)) {
453 if (NS_SUCCEEDED(idn
->ConvertUTF8toACE(hostname
, hostACE
)))
457 nsCOMPtr
<nsIDNSListener
> listenerProxy
;
459 rv
= NS_GetProxyForObject(target
,
460 NS_GET_IID(nsIDNSListener
),
462 NS_PROXY_ASYNC
| NS_PROXY_ALWAYS
,
463 getter_AddRefs(listenerProxy
));
464 if (NS_FAILED(rv
)) return rv
;
465 listener
= listenerProxy
;
468 PRUint16 af
= GetAFForLookup(*hostPtr
);
470 nsDNSAsyncRequest
*req
=
471 new nsDNSAsyncRequest(res
, *hostPtr
, listener
, flags
, af
);
473 return NS_ERROR_OUT_OF_MEMORY
;
474 NS_ADDREF(*result
= req
);
476 // addref for resolver; will be released when OnLookupComplete is called.
478 rv
= res
->ResolveHost(req
->mHost
.get(), flags
, af
, req
);
487 nsDNSService::Resolve(const nsACString
&hostname
,
489 nsIDNSRecord
**result
)
491 // grab reference to global host resolver and IDN service. beware
492 // simultaneous shutdown!!
493 nsRefPtr
<nsHostResolver
> res
;
494 nsCOMPtr
<nsIIDNService
> idn
;
496 nsAutoLock
lock(mLock
);
500 NS_ENSURE_TRUE(res
, NS_ERROR_OFFLINE
);
502 const nsACString
*hostPtr
= &hostname
;
505 nsCAutoString hostACE
;
506 if (idn
&& !IsASCII(hostname
)) {
507 if (NS_SUCCEEDED(idn
->ConvertUTF8toACE(hostname
, hostACE
)))
512 // sync resolve: since the host resolver only works asynchronously, we need
513 // to use a mutex and a condvar to wait for the result. however, since the
514 // result may be in the resolvers cache, we might get called back recursively
515 // on the same thread. so, our mutex needs to be re-entrant. in other words,
516 // we need to use a monitor! ;-)
519 PRMonitor
*mon
= PR_NewMonitor();
521 return NS_ERROR_OUT_OF_MEMORY
;
523 PR_EnterMonitor(mon
);
524 nsDNSSyncRequest
syncReq(mon
);
526 PRUint16 af
= GetAFForLookup(*hostPtr
);
528 rv
= res
->ResolveHost(PromiseFlatCString(*hostPtr
).get(), flags
, af
, &syncReq
);
529 if (NS_SUCCEEDED(rv
)) {
531 while (!syncReq
.mDone
)
532 PR_Wait(mon
, PR_INTERVAL_NO_TIMEOUT
);
534 if (NS_FAILED(syncReq
.mStatus
))
535 rv
= syncReq
.mStatus
;
537 NS_ASSERTION(syncReq
.mHostRecord
, "no host record");
538 nsDNSRecord
*rec
= new nsDNSRecord(syncReq
.mHostRecord
);
540 rv
= NS_ERROR_OUT_OF_MEMORY
;
542 NS_ADDREF(*result
= rec
);
547 PR_DestroyMonitor(mon
);
552 nsDNSService::GetMyHostName(nsACString
&result
)
555 if (PR_GetSystemInfo(PR_SI_HOSTNAME
, name
, sizeof(name
)) == PR_SUCCESS
) {
559 return NS_ERROR_FAILURE
;
563 nsDNSService::Observe(nsISupports
*subject
, const char *topic
, const PRUnichar
*data
)
565 // we are only getting called if a preference has changed.
566 NS_ASSERTION(strcmp(topic
, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID
) == 0,
567 "unexpected observe call");
570 // Shutdown and this function are both only called on the UI thread, so we don't
571 // have to worry about mResolver being cleared out from under us.
573 // NOTE Shutting down and reinitializing the service like this is obviously
574 // suboptimal if Observe gets called several times in a row, but we don't
575 // expect that to be the case.
586 nsDNSService::GetAFForLookup(const nsACString
&host
)
591 nsAutoLock
lock(mLock
);
593 PRUint16 af
= PR_AF_UNSPEC
;
595 if (!mIPv4OnlyDomains
.IsEmpty()) {
596 const char *domain
, *domainEnd
, *end
;
597 PRUint32 hostLen
, domainLen
;
599 // see if host is in one of the IPv4-only domains
600 domain
= mIPv4OnlyDomains
.BeginReading();
601 domainEnd
= mIPv4OnlyDomains
.EndReading();
603 nsACString::const_iterator hostStart
;
604 host
.BeginReading(hostStart
);
605 hostLen
= host
.Length();
608 // skip any whitespace
609 while (*domain
== ' ' || *domain
== '\t')
612 // find end of this domain in the string
613 end
= strchr(domain
, ',');
617 // to see if the hostname is in the domain, check if the domain
618 // matches the end of the hostname.
619 domainLen
= end
- domain
;
620 if (domainLen
&& hostLen
>= domainLen
) {
621 const char *hostTail
= hostStart
.get() + hostLen
- domainLen
;
622 if (PL_strncasecmp(domain
, hostTail
, domainLen
) == 0) {
623 // now, make sure either that the hostname is a direct match or
624 // that the hostname begins with a dot.
625 if (hostLen
== domainLen
||
626 *hostTail
== '.' || *(hostTail
- 1) == '.') {