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"
61 #include "mozilla/FunctionTimer.h"
63 static const char kPrefDnsCacheEntries
[] = "network.dnsCacheEntries";
64 static const char kPrefDnsCacheExpiration
[] = "network.dnsCacheExpiration";
65 static const char kPrefEnableIDN
[] = "network.enableIDN";
66 static const char kPrefIPv4OnlyDomains
[] = "network.dns.ipv4OnlyDomains";
67 static const char kPrefDisableIPv6
[] = "network.dns.disableIPv6";
68 static const char kPrefDisablePrefetch
[] = "network.dns.disablePrefetch";
70 //-----------------------------------------------------------------------------
72 class nsDNSRecord
: public nsIDNSRecord
78 nsDNSRecord(nsHostRecord
*hostRecord
)
79 : mHostRecord(hostRecord
)
85 virtual ~nsDNSRecord() {}
87 nsRefPtr
<nsHostRecord
> mHostRecord
;
89 int mIterGenCnt
; // the generation count of
90 // mHostRecord->addr_info when we
95 NS_IMPL_THREADSAFE_ISUPPORTS1(nsDNSRecord
, nsIDNSRecord
)
98 nsDNSRecord::GetCanonicalName(nsACString
&result
)
100 // this method should only be called if we have a CNAME
101 NS_ENSURE_TRUE(mHostRecord
->flags
& nsHostResolver::RES_CANON_NAME
,
102 NS_ERROR_NOT_AVAILABLE
);
104 // if the record is for an IP address literal, then the canonical
105 // host name is the IP address literal.
107 PR_Lock(mHostRecord
->addr_info_lock
);
108 if (mHostRecord
->addr_info
)
109 cname
= PR_GetCanonNameFromAddrInfo(mHostRecord
->addr_info
);
111 cname
= mHostRecord
->host
;
112 result
.Assign(cname
);
113 PR_Unlock(mHostRecord
->addr_info_lock
);
118 nsDNSRecord::GetNextAddr(PRUint16 port
, PRNetAddr
*addr
)
120 // not a programming error to poke the DNS record when it has no more
121 // entries. just fail without any debug warnings. this enables consumers
122 // to enumerate the DNS record without calling HasMore.
124 return NS_ERROR_NOT_AVAILABLE
;
126 PR_Lock(mHostRecord
->addr_info_lock
);
127 if (mHostRecord
->addr_info
) {
129 mIterGenCnt
= mHostRecord
->addr_info_gencnt
;
130 else if (mIterGenCnt
!= mHostRecord
->addr_info_gencnt
) {
131 // mHostRecord->addr_info has changed, so mIter is invalid.
132 // Restart the iteration. Alternatively, we could just fail.
134 mIterGenCnt
= mHostRecord
->addr_info_gencnt
;
136 mIter
= PR_EnumerateAddrInfo(mIter
, mHostRecord
->addr_info
, port
, addr
);
137 PR_Unlock(mHostRecord
->addr_info_lock
);
140 return NS_ERROR_NOT_AVAILABLE
;
144 PR_Unlock(mHostRecord
->addr_info_lock
);
145 if (!mHostRecord
->addr
) {
146 // Both mHostRecord->addr_info and mHostRecord->addr are null.
147 // This can happen if mHostRecord->addr_info expired and the
148 // attempt to reresolve it failed.
149 return NS_ERROR_NOT_AVAILABLE
;
151 memcpy(addr
, mHostRecord
->addr
, sizeof(PRNetAddr
));
153 port
= PR_htons(port
);
154 if (addr
->raw
.family
== PR_AF_INET
)
155 addr
->inet
.port
= port
;
157 addr
->ipv6
.port
= port
;
158 mDone
= PR_TRUE
; // no iterations
165 nsDNSRecord::GetNextAddrAsString(nsACString
&result
)
168 nsresult rv
= GetNextAddr(0, &addr
);
169 if (NS_FAILED(rv
)) return rv
;
172 if (PR_NetAddrToString(&addr
, buf
, sizeof(buf
)) == PR_SUCCESS
) {
176 NS_ERROR("PR_NetAddrToString failed unexpectedly");
177 return NS_ERROR_FAILURE
; // conversion failed for some reason
181 nsDNSRecord::HasMore(PRBool
*result
)
186 // unfortunately, NSPR does not provide a way for us to determine if
187 // there is another address other than to simply get the next address.
188 void *iterCopy
= mIter
;
190 *result
= NS_SUCCEEDED(GetNextAddr(0, &addr
));
191 mIter
= iterCopy
; // backup iterator
198 nsDNSRecord::Rewind()
206 //-----------------------------------------------------------------------------
208 class nsDNSAsyncRequest
: public nsResolveHostCallback
209 , public nsICancelable
213 NS_DECL_NSICANCELABLE
215 nsDNSAsyncRequest(nsHostResolver
*res
,
216 const nsACString
&host
,
217 nsIDNSListener
*listener
,
222 , mListener(listener
)
225 ~nsDNSAsyncRequest() {}
227 void OnLookupComplete(nsHostResolver
*, nsHostRecord
*, nsresult
);
229 nsRefPtr
<nsHostResolver
> mResolver
;
230 nsCString mHost
; // hostname we're resolving
231 nsCOMPtr
<nsIDNSListener
> mListener
;
237 nsDNSAsyncRequest::OnLookupComplete(nsHostResolver
*resolver
,
238 nsHostRecord
*hostRecord
,
241 // need to have an owning ref when we issue the callback to enable
242 // the caller to be able to addref/release multiple times without
243 // destroying the record prematurely.
244 nsCOMPtr
<nsIDNSRecord
> rec
;
245 if (NS_SUCCEEDED(status
)) {
246 NS_ASSERTION(hostRecord
, "no host record");
247 rec
= new nsDNSRecord(hostRecord
);
249 status
= NS_ERROR_OUT_OF_MEMORY
;
252 mListener
->OnLookupComplete(this, rec
, status
);
255 // release the reference to ourselves that was added before we were
256 // handed off to the host resolver.
260 NS_IMPL_THREADSAFE_ISUPPORTS1(nsDNSAsyncRequest
, nsICancelable
)
263 nsDNSAsyncRequest::Cancel(nsresult reason
)
265 NS_ENSURE_ARG(NS_FAILED(reason
));
266 mResolver
->DetachCallback(mHost
.get(), mFlags
, mAF
, this, reason
);
270 //-----------------------------------------------------------------------------
272 class nsDNSSyncRequest
: public nsResolveHostCallback
275 nsDNSSyncRequest(PRMonitor
*mon
)
279 virtual ~nsDNSSyncRequest() {}
281 void OnLookupComplete(nsHostResolver
*, nsHostRecord
*, nsresult
);
285 nsRefPtr
<nsHostRecord
> mHostRecord
;
292 nsDNSSyncRequest::OnLookupComplete(nsHostResolver
*resolver
,
293 nsHostRecord
*hostRecord
,
296 // store results, and wake up nsDNSService::Resolve to process results.
297 PR_EnterMonitor(mMonitor
);
300 mHostRecord
= hostRecord
;
302 PR_ExitMonitor(mMonitor
);
305 //-----------------------------------------------------------------------------
307 nsDNSService::nsDNSService()
312 nsDNSService::~nsDNSService()
315 PR_DestroyLock(mLock
);
318 NS_IMPL_THREADSAFE_ISUPPORTS3(nsDNSService
, nsIDNSService
, nsPIDNSService
,
326 NS_ENSURE_TRUE(!mResolver
, NS_ERROR_ALREADY_INITIALIZED
);
328 PRBool firstTime
= (mLock
== nsnull
);
331 PRUint32 maxCacheEntries
= 400;
332 PRUint32 maxCacheLifetime
= 3; // minutes
333 PRBool enableIDN
= PR_TRUE
;
334 PRBool disableIPv6
= PR_FALSE
;
335 PRBool disablePrefetch
= PR_FALSE
;
336 int proxyType
= nsIProtocolProxyService::PROXYCONFIG_DIRECT
;
338 nsAdoptingCString ipv4OnlyDomains
;
341 nsCOMPtr
<nsIPrefBranch2
> prefs
= do_GetService(NS_PREFSERVICE_CONTRACTID
);
344 if (NS_SUCCEEDED(prefs
->GetIntPref(kPrefDnsCacheEntries
, &val
)))
345 maxCacheEntries
= (PRUint32
) val
;
346 if (NS_SUCCEEDED(prefs
->GetIntPref(kPrefDnsCacheExpiration
, &val
)))
347 maxCacheLifetime
= val
/ 60; // convert from seconds to minutes
349 // ASSUMPTION: pref branch does not modify out params on failure
350 prefs
->GetBoolPref(kPrefEnableIDN
, &enableIDN
);
351 prefs
->GetBoolPref(kPrefDisableIPv6
, &disableIPv6
);
352 prefs
->GetCharPref(kPrefIPv4OnlyDomains
, getter_Copies(ipv4OnlyDomains
));
353 prefs
->GetBoolPref(kPrefDisablePrefetch
, &disablePrefetch
);
355 // If a manual proxy is in use, disable prefetch implicitly
356 prefs
->GetIntPref("network.proxy.type", &proxyType
);
360 mLock
= PR_NewLock();
362 return NS_ERROR_OUT_OF_MEMORY
;
364 // register as prefs observer
366 prefs
->AddObserver(kPrefDnsCacheEntries
, this, PR_FALSE
);
367 prefs
->AddObserver(kPrefDnsCacheExpiration
, this, PR_FALSE
);
368 prefs
->AddObserver(kPrefEnableIDN
, this, PR_FALSE
);
369 prefs
->AddObserver(kPrefIPv4OnlyDomains
, this, PR_FALSE
);
370 prefs
->AddObserver(kPrefDisableIPv6
, this, PR_FALSE
);
371 prefs
->AddObserver(kPrefDisablePrefetch
, this, PR_FALSE
);
373 // Monitor these to see if there is a change in proxy configuration
374 // If a manual proxy is in use, disable prefetch implicitly
375 prefs
->AddObserver("network.proxy.type", this, PR_FALSE
);
379 // we have to null out mIDN since we might be getting re-initialized
380 // as a result of a pref change.
381 nsCOMPtr
<nsIIDNService
> idn
;
383 idn
= do_GetService(NS_IDNSERVICE_CONTRACTID
);
385 nsRefPtr
<nsHostResolver
> res
;
386 nsresult rv
= nsHostResolver::Create(maxCacheEntries
,
388 getter_AddRefs(res
));
389 if (NS_SUCCEEDED(rv
)) {
390 // now, set all of our member variables while holding the lock
391 nsAutoLock
lock(mLock
);
394 mIPv4OnlyDomains
= ipv4OnlyDomains
; // exchanges buffer ownership
395 mDisableIPv6
= disableIPv6
;
397 // Disable prefetching either by explicit preference or if a manual proxy is configured
398 mDisablePrefetch
= disablePrefetch
|| (proxyType
== nsIProtocolProxyService::PROXYCONFIG_MANUAL
);
401 nsDNSPrefetch::Initialize(this);
406 nsDNSService::Shutdown()
408 nsRefPtr
<nsHostResolver
> res
;
410 nsAutoLock
lock(mLock
);
420 nsDNSService::AsyncResolve(const nsACString
&hostname
,
422 nsIDNSListener
*listener
,
423 nsIEventTarget
*target
,
424 nsICancelable
**result
)
426 // grab reference to global host resolver and IDN service. beware
427 // simultaneous shutdown!!
428 nsRefPtr
<nsHostResolver
> res
;
429 nsCOMPtr
<nsIIDNService
> idn
;
431 nsAutoLock
lock(mLock
);
433 if (mDisablePrefetch
&& (flags
& RESOLVE_SPECULATE
))
434 return NS_ERROR_DNS_LOOKUP_QUEUE_FULL
;
440 return NS_ERROR_OFFLINE
;
442 const nsACString
*hostPtr
= &hostname
;
445 nsCAutoString hostACE
;
446 if (idn
&& !IsASCII(hostname
)) {
447 if (NS_SUCCEEDED(idn
->ConvertUTF8toACE(hostname
, hostACE
)))
451 nsCOMPtr
<nsIDNSListener
> listenerProxy
;
453 rv
= NS_GetProxyForObject(target
,
454 NS_GET_IID(nsIDNSListener
),
456 NS_PROXY_ASYNC
| NS_PROXY_ALWAYS
,
457 getter_AddRefs(listenerProxy
));
458 if (NS_FAILED(rv
)) return rv
;
459 listener
= listenerProxy
;
462 PRUint16 af
= GetAFForLookup(*hostPtr
);
464 nsDNSAsyncRequest
*req
=
465 new nsDNSAsyncRequest(res
, *hostPtr
, listener
, flags
, af
);
467 return NS_ERROR_OUT_OF_MEMORY
;
468 NS_ADDREF(*result
= req
);
470 // addref for resolver; will be released when OnLookupComplete is called.
472 rv
= res
->ResolveHost(req
->mHost
.get(), flags
, af
, req
);
481 nsDNSService::Resolve(const nsACString
&hostname
,
483 nsIDNSRecord
**result
)
485 // grab reference to global host resolver and IDN service. beware
486 // simultaneous shutdown!!
487 nsRefPtr
<nsHostResolver
> res
;
488 nsCOMPtr
<nsIIDNService
> idn
;
490 nsAutoLock
lock(mLock
);
494 NS_ENSURE_TRUE(res
, NS_ERROR_OFFLINE
);
496 const nsACString
*hostPtr
= &hostname
;
499 nsCAutoString hostACE
;
500 if (idn
&& !IsASCII(hostname
)) {
501 if (NS_SUCCEEDED(idn
->ConvertUTF8toACE(hostname
, hostACE
)))
506 // sync resolve: since the host resolver only works asynchronously, we need
507 // to use a mutex and a condvar to wait for the result. however, since the
508 // result may be in the resolvers cache, we might get called back recursively
509 // on the same thread. so, our mutex needs to be re-entrant. in other words,
510 // we need to use a monitor! ;-)
513 PRMonitor
*mon
= PR_NewMonitor();
515 return NS_ERROR_OUT_OF_MEMORY
;
517 PR_EnterMonitor(mon
);
518 nsDNSSyncRequest
syncReq(mon
);
520 PRUint16 af
= GetAFForLookup(*hostPtr
);
522 rv
= res
->ResolveHost(PromiseFlatCString(*hostPtr
).get(), flags
, af
, &syncReq
);
523 if (NS_SUCCEEDED(rv
)) {
525 while (!syncReq
.mDone
)
526 PR_Wait(mon
, PR_INTERVAL_NO_TIMEOUT
);
528 if (NS_FAILED(syncReq
.mStatus
))
529 rv
= syncReq
.mStatus
;
531 NS_ASSERTION(syncReq
.mHostRecord
, "no host record");
532 nsDNSRecord
*rec
= new nsDNSRecord(syncReq
.mHostRecord
);
534 rv
= NS_ERROR_OUT_OF_MEMORY
;
536 NS_ADDREF(*result
= rec
);
541 PR_DestroyMonitor(mon
);
546 nsDNSService::GetMyHostName(nsACString
&result
)
549 if (PR_GetSystemInfo(PR_SI_HOSTNAME
, name
, sizeof(name
)) == PR_SUCCESS
) {
553 return NS_ERROR_FAILURE
;
557 nsDNSService::Observe(nsISupports
*subject
, const char *topic
, const PRUnichar
*data
)
559 // we are only getting called if a preference has changed.
560 NS_ASSERTION(strcmp(topic
, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID
) == 0,
561 "unexpected observe call");
564 // Shutdown and this function are both only called on the UI thread, so we don't
565 // have to worry about mResolver being cleared out from under us.
567 // NOTE Shutting down and reinitializing the service like this is obviously
568 // suboptimal if Observe gets called several times in a row, but we don't
569 // expect that to be the case.
580 nsDNSService::GetAFForLookup(const nsACString
&host
)
585 nsAutoLock
lock(mLock
);
587 PRUint16 af
= PR_AF_UNSPEC
;
589 if (!mIPv4OnlyDomains
.IsEmpty()) {
590 const char *domain
, *domainEnd
, *end
;
591 PRUint32 hostLen
, domainLen
;
593 // see if host is in one of the IPv4-only domains
594 domain
= mIPv4OnlyDomains
.BeginReading();
595 domainEnd
= mIPv4OnlyDomains
.EndReading();
597 nsACString::const_iterator hostStart
;
598 host
.BeginReading(hostStart
);
599 hostLen
= host
.Length();
602 // skip any whitespace
603 while (*domain
== ' ' || *domain
== '\t')
606 // find end of this domain in the string
607 end
= strchr(domain
, ',');
611 // to see if the hostname is in the domain, check if the domain
612 // matches the end of the hostname.
613 domainLen
= end
- domain
;
614 if (domainLen
&& hostLen
>= domainLen
) {
615 const char *hostTail
= hostStart
.get() + hostLen
- domainLen
;
616 if (PL_strncasecmp(domain
, hostTail
, domainLen
) == 0) {
617 // now, make sure either that the hostname is a direct match or
618 // that the hostname begins with a dot.
619 if (hostLen
== domainLen
||
620 *hostTail
== '.' || *(hostTail
- 1) == '.') {