Bug 613131 - role nothing should allow recursive name calculation from children,...
[mozilla-central.git] / netwerk / dns / nsDNSService2.cpp
blobadf1ba1df25b291898ef5d36f1a7d879160ad40d
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
13 * License.
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.
21 * Contributor(s):
22 * IBM Corp.
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"
48 #include "nsString.h"
49 #include "nsAutoLock.h"
50 #include "nsAutoPtr.h"
51 #include "nsNetCID.h"
52 #include "nsNetError.h"
53 #include "nsDNSPrefetch.h"
54 #include "nsIProtocolProxyService.h"
55 #include "prsystem.h"
56 #include "prnetdb.h"
57 #include "prmon.h"
58 #include "prio.h"
59 #include "plstr.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
74 public:
75 NS_DECL_ISUPPORTS
76 NS_DECL_NSIDNSRECORD
78 nsDNSRecord(nsHostRecord *hostRecord)
79 : mHostRecord(hostRecord)
80 , mIter(nsnull)
81 , mIterGenCnt(-1)
82 , mDone(PR_FALSE) {}
84 private:
85 virtual ~nsDNSRecord() {}
87 nsRefPtr<nsHostRecord> mHostRecord;
88 void *mIter;
89 int mIterGenCnt; // the generation count of
90 // mHostRecord->addr_info when we
91 // start iterating
92 PRBool mDone;
95 NS_IMPL_THREADSAFE_ISUPPORTS1(nsDNSRecord, nsIDNSRecord)
97 NS_IMETHODIMP
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.
106 const char *cname;
107 PR_Lock(mHostRecord->addr_info_lock);
108 if (mHostRecord->addr_info)
109 cname = PR_GetCanonNameFromAddrInfo(mHostRecord->addr_info);
110 else
111 cname = mHostRecord->host;
112 result.Assign(cname);
113 PR_Unlock(mHostRecord->addr_info_lock);
114 return NS_OK;
117 NS_IMETHODIMP
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.
123 if (mDone)
124 return NS_ERROR_NOT_AVAILABLE;
126 PR_Lock(mHostRecord->addr_info_lock);
127 if (mHostRecord->addr_info) {
128 if (!mIter)
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.
133 mIter = nsnull;
134 mIterGenCnt = mHostRecord->addr_info_gencnt;
136 mIter = PR_EnumerateAddrInfo(mIter, mHostRecord->addr_info, port, addr);
137 PR_Unlock(mHostRecord->addr_info_lock);
138 if (!mIter) {
139 mDone = PR_TRUE;
140 return NS_ERROR_NOT_AVAILABLE;
143 else {
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));
152 // set given port
153 port = PR_htons(port);
154 if (addr->raw.family == PR_AF_INET)
155 addr->inet.port = port;
156 else
157 addr->ipv6.port = port;
158 mDone = PR_TRUE; // no iterations
161 return NS_OK;
164 NS_IMETHODIMP
165 nsDNSRecord::GetNextAddrAsString(nsACString &result)
167 PRNetAddr addr;
168 nsresult rv = GetNextAddr(0, &addr);
169 if (NS_FAILED(rv)) return rv;
171 char buf[64];
172 if (PR_NetAddrToString(&addr, buf, sizeof(buf)) == PR_SUCCESS) {
173 result.Assign(buf);
174 return NS_OK;
176 NS_ERROR("PR_NetAddrToString failed unexpectedly");
177 return NS_ERROR_FAILURE; // conversion failed for some reason
180 NS_IMETHODIMP
181 nsDNSRecord::HasMore(PRBool *result)
183 if (mDone)
184 *result = PR_FALSE;
185 else {
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;
189 PRNetAddr addr;
190 *result = NS_SUCCEEDED(GetNextAddr(0, &addr));
191 mIter = iterCopy; // backup iterator
192 mDone = PR_FALSE;
194 return NS_OK;
197 NS_IMETHODIMP
198 nsDNSRecord::Rewind()
200 mIter = nsnull;
201 mIterGenCnt = -1;
202 mDone = PR_FALSE;
203 return NS_OK;
206 //-----------------------------------------------------------------------------
208 class nsDNSAsyncRequest : public nsResolveHostCallback
209 , public nsICancelable
211 public:
212 NS_DECL_ISUPPORTS
213 NS_DECL_NSICANCELABLE
215 nsDNSAsyncRequest(nsHostResolver *res,
216 const nsACString &host,
217 nsIDNSListener *listener,
218 PRUint16 flags,
219 PRUint16 af)
220 : mResolver(res)
221 , mHost(host)
222 , mListener(listener)
223 , mFlags(flags)
224 , mAF(af) {}
225 ~nsDNSAsyncRequest() {}
227 void OnLookupComplete(nsHostResolver *, nsHostRecord *, nsresult);
229 nsRefPtr<nsHostResolver> mResolver;
230 nsCString mHost; // hostname we're resolving
231 nsCOMPtr<nsIDNSListener> mListener;
232 PRUint16 mFlags;
233 PRUint16 mAF;
236 void
237 nsDNSAsyncRequest::OnLookupComplete(nsHostResolver *resolver,
238 nsHostRecord *hostRecord,
239 nsresult status)
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);
248 if (!rec)
249 status = NS_ERROR_OUT_OF_MEMORY;
252 mListener->OnLookupComplete(this, rec, status);
253 mListener = nsnull;
255 // release the reference to ourselves that was added before we were
256 // handed off to the host resolver.
257 NS_RELEASE_THIS();
260 NS_IMPL_THREADSAFE_ISUPPORTS1(nsDNSAsyncRequest, nsICancelable)
262 NS_IMETHODIMP
263 nsDNSAsyncRequest::Cancel(nsresult reason)
265 NS_ENSURE_ARG(NS_FAILED(reason));
266 mResolver->DetachCallback(mHost.get(), mFlags, mAF, this, reason);
267 return NS_OK;
270 //-----------------------------------------------------------------------------
272 class nsDNSSyncRequest : public nsResolveHostCallback
274 public:
275 nsDNSSyncRequest(PRMonitor *mon)
276 : mDone(PR_FALSE)
277 , mStatus(NS_OK)
278 , mMonitor(mon) {}
279 virtual ~nsDNSSyncRequest() {}
281 void OnLookupComplete(nsHostResolver *, nsHostRecord *, nsresult);
283 PRBool mDone;
284 nsresult mStatus;
285 nsRefPtr<nsHostRecord> mHostRecord;
287 private:
288 PRMonitor *mMonitor;
291 void
292 nsDNSSyncRequest::OnLookupComplete(nsHostResolver *resolver,
293 nsHostRecord *hostRecord,
294 nsresult status)
296 // store results, and wake up nsDNSService::Resolve to process results.
297 PR_EnterMonitor(mMonitor);
298 mDone = PR_TRUE;
299 mStatus = status;
300 mHostRecord = hostRecord;
301 PR_Notify(mMonitor);
302 PR_ExitMonitor(mMonitor);
305 //-----------------------------------------------------------------------------
307 nsDNSService::nsDNSService()
308 : mLock(nsnull)
312 nsDNSService::~nsDNSService()
314 if (mLock)
315 PR_DestroyLock(mLock);
318 NS_IMPL_THREADSAFE_ISUPPORTS3(nsDNSService, nsIDNSService, nsPIDNSService,
319 nsIObserver)
321 NS_IMETHODIMP
322 nsDNSService::Init()
324 NS_TIME_FUNCTION;
326 NS_ENSURE_TRUE(!mResolver, NS_ERROR_ALREADY_INITIALIZED);
328 PRBool firstTime = (mLock == nsnull);
330 // prefs
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;
340 // read prefs
341 nsCOMPtr<nsIPrefBranch2> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
342 if (prefs) {
343 PRInt32 val;
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);
359 if (firstTime) {
360 mLock = PR_NewLock();
361 if (!mLock)
362 return NS_ERROR_OUT_OF_MEMORY;
364 // register as prefs observer
365 if (prefs) {
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;
382 if (enableIDN)
383 idn = do_GetService(NS_IDNSERVICE_CONTRACTID);
385 nsRefPtr<nsHostResolver> res;
386 nsresult rv = nsHostResolver::Create(maxCacheEntries,
387 maxCacheLifetime,
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);
392 mResolver = res;
393 mIDN = idn;
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);
402 return rv;
405 NS_IMETHODIMP
406 nsDNSService::Shutdown()
408 nsRefPtr<nsHostResolver> res;
410 nsAutoLock lock(mLock);
411 res = mResolver;
412 mResolver = nsnull;
414 if (res)
415 res->Shutdown();
416 return NS_OK;
419 NS_IMETHODIMP
420 nsDNSService::AsyncResolve(const nsACString &hostname,
421 PRUint32 flags,
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;
436 res = mResolver;
437 idn = mIDN;
439 if (!res)
440 return NS_ERROR_OFFLINE;
442 const nsACString *hostPtr = &hostname;
444 nsresult rv;
445 nsCAutoString hostACE;
446 if (idn && !IsASCII(hostname)) {
447 if (NS_SUCCEEDED(idn->ConvertUTF8toACE(hostname, hostACE)))
448 hostPtr = &hostACE;
451 nsCOMPtr<nsIDNSListener> listenerProxy;
452 if (target) {
453 rv = NS_GetProxyForObject(target,
454 NS_GET_IID(nsIDNSListener),
455 listener,
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);
466 if (!req)
467 return NS_ERROR_OUT_OF_MEMORY;
468 NS_ADDREF(*result = req);
470 // addref for resolver; will be released when OnLookupComplete is called.
471 NS_ADDREF(req);
472 rv = res->ResolveHost(req->mHost.get(), flags, af, req);
473 if (NS_FAILED(rv)) {
474 NS_RELEASE(req);
475 NS_RELEASE(*result);
477 return rv;
480 NS_IMETHODIMP
481 nsDNSService::Resolve(const nsACString &hostname,
482 PRUint32 flags,
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);
491 res = mResolver;
492 idn = mIDN;
494 NS_ENSURE_TRUE(res, NS_ERROR_OFFLINE);
496 const nsACString *hostPtr = &hostname;
498 nsresult rv;
499 nsCAutoString hostACE;
500 if (idn && !IsASCII(hostname)) {
501 if (NS_SUCCEEDED(idn->ConvertUTF8toACE(hostname, hostACE)))
502 hostPtr = &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();
514 if (!mon)
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)) {
524 // wait for result
525 while (!syncReq.mDone)
526 PR_Wait(mon, PR_INTERVAL_NO_TIMEOUT);
528 if (NS_FAILED(syncReq.mStatus))
529 rv = syncReq.mStatus;
530 else {
531 NS_ASSERTION(syncReq.mHostRecord, "no host record");
532 nsDNSRecord *rec = new nsDNSRecord(syncReq.mHostRecord);
533 if (!rec)
534 rv = NS_ERROR_OUT_OF_MEMORY;
535 else
536 NS_ADDREF(*result = rec);
540 PR_ExitMonitor(mon);
541 PR_DestroyMonitor(mon);
542 return rv;
545 NS_IMETHODIMP
546 nsDNSService::GetMyHostName(nsACString &result)
548 char name[100];
549 if (PR_GetSystemInfo(PR_SI_HOSTNAME, name, sizeof(name)) == PR_SUCCESS) {
550 result = name;
551 return NS_OK;
553 return NS_ERROR_FAILURE;
556 NS_IMETHODIMP
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.
572 if (mResolver) {
573 Shutdown();
574 Init();
576 return NS_OK;
579 PRUint16
580 nsDNSService::GetAFForLookup(const nsACString &host)
582 if (mDisableIPv6)
583 return PR_AF_INET;
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();
601 do {
602 // skip any whitespace
603 while (*domain == ' ' || *domain == '\t')
604 ++domain;
606 // find end of this domain in the string
607 end = strchr(domain, ',');
608 if (!end)
609 end = domainEnd;
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) == '.') {
621 af = PR_AF_INET;
622 break;
627 domain = end + 1;
628 } while (*end);
631 return af;